/* * Copyright © 2020 Drew Devault * Copyright © 2021 Xaver Hugl * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of the * copyright holders not be used in advertising or publicity * pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ #include #include "os/client_priv.h" #ifdef WITH_LIBDRM #include #include #endif #include "randrstr_priv.h" #include "xwayland-drm-lease.h" #include "xwayland-screen.h" #include "xwayland-output.h" static void xwl_randr_lease_cleanup_outputs(RRLeasePtr rrLease) { struct xwl_output *output; int i; for (i = 0; i < rrLease->numOutputs; ++i) { output = rrLease->outputs[i]->devPrivate; if (output) { output->lease = NULL; } } } static void xwl_randr_lease_free_outputs(RRLeasePtr rrLease) { struct xwl_output *xwl_output; int i; for (i = 0; i < rrLease->numOutputs; ++i) { xwl_output = rrLease->outputs[i]->devPrivate; if (xwl_output && xwl_output->withdrawn_connector) { rrLease->outputs[i]->devPrivate = NULL; xwl_output_remove(xwl_output); } } } static void drm_lease_handle_lease_fd(void *data, struct wp_drm_lease_v1 *wp_drm_lease_v1, int32_t lease_fd) { struct xwl_drm_lease *lease = (struct xwl_drm_lease *)data; lease->fd = lease_fd; AttendClient(lease->client); } static void drm_lease_handle_finished(void *data, struct wp_drm_lease_v1 *wp_drm_lease_v1) { struct xwl_drm_lease *lease = (struct xwl_drm_lease *)data; if (lease->fd >= 0) { RRTerminateLease(lease->rrLease); } else { AttendClient(lease->client); xwl_randr_lease_cleanup_outputs(lease->rrLease); } /* Free the xwl_outputs that have been withdrawn while lease-able */ xwl_randr_lease_free_outputs(lease->rrLease); } static struct wp_drm_lease_v1_listener drm_lease_listener = { .lease_fd = drm_lease_handle_lease_fd, .finished = drm_lease_handle_finished, }; void xwl_randr_get_lease(ClientPtr client, ScreenPtr screen, RRLeasePtr *rrLease, int *fd) { struct xwl_screen *xwl_screen; struct xwl_drm_lease *lease; xwl_screen = xwl_screen_get(screen); xorg_list_for_each_entry(lease, &xwl_screen->drm_leases, link) { if (lease->client == client) { *rrLease = lease->rrLease; *fd = lease->fd; if (lease->fd < 0) xorg_list_del(&lease->link); return; } } *rrLease = NULL; *fd = -1; } int xwl_randr_request_lease(ClientPtr client, ScreenPtr screen, RRLeasePtr rrLease) { struct xwl_screen *xwl_screen; struct wp_drm_lease_request_v1 *req; struct xwl_drm_lease *lease_private; struct xwl_drm_lease_device *lease_device = NULL; struct xwl_drm_lease_device *device_data; struct xwl_output *output; int i; xwl_screen = xwl_screen_get(screen); if (xorg_list_is_empty(&xwl_screen->drm_lease_devices)) { ErrorF("Attempted to create DRM lease without wp_drm_lease_device_v1\n"); return BadMatch; } for (i = 0; i < rrLease->numOutputs; ++i) { output = rrLease->outputs[i]->devPrivate; if (!output || !output->lease_connector || output->lease) { return BadValue; } } xorg_list_for_each_entry(device_data, &xwl_screen->drm_lease_devices, link) { Bool connectors_of_device = FALSE; for (i = 0; i < rrLease->numOutputs; ++i) { output = rrLease->outputs[i]->devPrivate; if (output->lease_device == device_data) { connectors_of_device = TRUE; break; } } if (connectors_of_device) { if (lease_device != NULL) { ErrorF("Attempted to create DRM lease from multiple devices\n"); return BadValue; } lease_device = device_data; } } req = wp_drm_lease_device_v1_create_lease_request( lease_device->drm_lease_device); lease_private = calloc(1, sizeof(struct xwl_drm_lease)); for (i = 0; i < rrLease->numOutputs; ++i) { output = rrLease->outputs[i]->devPrivate; output->lease = lease_private; wp_drm_lease_request_v1_request_connector(req, output->lease_connector); } lease_private->fd = -1; lease_private->lease = wp_drm_lease_request_v1_submit(req); lease_private->rrLease = rrLease; lease_private->client = client; rrLease->devPrivate = lease_private; wp_drm_lease_v1_add_listener(lease_private->lease, &drm_lease_listener, lease_private); xorg_list_add(&lease_private->link, &xwl_screen->drm_leases); ResetCurrentRequest(client); client->sequence--; IgnoreClient(client); return Success; } void xwl_randr_terminate_lease(ScreenPtr screen, RRLeasePtr lease) { struct xwl_drm_lease *lease_private = lease->devPrivate; if (lease_private) { xwl_randr_lease_cleanup_outputs(lease); xorg_list_del(&lease_private->link); if (lease_private->fd >= 0) close(lease_private->fd); wp_drm_lease_v1_destroy(lease_private->lease); free(lease_private); lease->devPrivate = NULL; } RRLeaseTerminated(lease); } static void lease_connector_handle_name(void *data, struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1, const char *name) { struct xwl_output *xwl_output = data; char rr_output_name[MAX_OUTPUT_NAME] = { 0 }; snprintf(rr_output_name, MAX_OUTPUT_NAME, "lease-%s", name); xwl_output_set_name(xwl_output, rr_output_name); } static void lease_connector_handle_description(void *data, struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1, const char *description) { /* This space is deliberately left blank */ } static RRModePtr * xwl_get_rrmodes_from_connector_id(int drm, int32_t connector_id, int *nmode, int *npref) { #ifdef WITH_LIBDRM drmModeConnectorPtr conn; drmModeModeInfoPtr kmode; RRModePtr *rrmodes; int pref, i; *nmode = *npref = 0; conn = drmModeGetConnectorCurrent(drm, connector_id); if (!conn) { ErrorF("drmModeGetConnector for connector %d failed\n", connector_id); return NULL; } rrmodes = xallocarray(conn->count_modes, sizeof(RRModePtr)); if (!rrmodes) { ErrorF("Failed to allocate connector modes\n"); drmModeFreeConnector(conn); return NULL; } /* This spaghetti brought to you courtesey of xf86RandrR12.c * It adds preferred modes first, then non-preferred modes */ for (pref = 1; pref >= 0; pref--) { for (i = 0; i < conn->count_modes; ++i) { kmode = &conn->modes[i]; if ((pref != 0) == ((kmode->type & DRM_MODE_TYPE_PREFERRED) != 0)) { xRRModeInfo modeInfo; RRModePtr rrmode; modeInfo.nameLength = strlen(kmode->name); modeInfo.width = kmode->hdisplay; modeInfo.dotClock = kmode->clock * 1000; modeInfo.hSyncStart = kmode->hsync_start; modeInfo.hSyncEnd = kmode->hsync_end; modeInfo.hTotal = kmode->htotal; modeInfo.hSkew = kmode->hskew; modeInfo.height = kmode->vdisplay; modeInfo.vSyncStart = kmode->vsync_start; modeInfo.vSyncEnd = kmode->vsync_end; modeInfo.vTotal = kmode->vtotal; modeInfo.modeFlags = kmode->flags; rrmode = RRModeGet(&modeInfo, kmode->name); if (rrmode) { rrmodes[*nmode] = rrmode; *nmode = *nmode + 1; *npref = *npref + pref; } } } } /* workaround: there could be no preferred mode that got added */ if (*nmode > 0 && *npref == 0) *npref = 1; drmModeFreeConnector(conn); return rrmodes; #else *nmode = *npref = 0; return NULL; #endif } static void lease_connector_handle_connector_id(void *data, struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1, uint32_t connector_id) { struct xwl_output *output; Atom name; INT32 value; int err; int nmode, npref; RRModePtr *rrmodes; value = connector_id; output = (struct xwl_output *)data; name = MakeAtom("CONNECTOR_ID", 12, TRUE); if (name != BAD_RESOURCE) { err = RRConfigureOutputProperty(output->randr_output, name, FALSE, FALSE, TRUE, 1, &value); if (err != 0) { ErrorF("RRConfigureOutputProperty error, %d\n", err); return; } err = RRChangeOutputProperty(output->randr_output, name, XA_INTEGER, 32, PropModeReplace, 1, &value, FALSE, FALSE); if (err != 0) { ErrorF("RRChangeOutputProperty error, %d\n", err); return; } } rrmodes = xwl_get_rrmodes_from_connector_id(output->lease_device->drm_read_only_fd, connector_id, &nmode, &npref); if (rrmodes != NULL) RROutputSetModes(output->randr_output, rrmodes, nmode, npref); free(rrmodes); } static void lease_connector_handle_done(void *data, struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1) { /* This space is deliberately left blank */ } static void lease_connector_handle_withdrawn(void *data, struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1) { struct xwl_output *xwl_output = data; xwl_output->withdrawn_connector = TRUE; /* Not removing the xwl_output if currently leased with Wayland */ if (xwl_output->lease) return; xwl_output_remove(xwl_output); } static const struct wp_drm_lease_connector_v1_listener lease_connector_listener = { .name = lease_connector_handle_name, .description = lease_connector_handle_description, .connector_id = lease_connector_handle_connector_id, .withdrawn = lease_connector_handle_withdrawn, .done = lease_connector_handle_done, }; static void drm_lease_device_handle_drm_fd(void *data, struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1, int fd) { ((struct xwl_drm_lease_device *)data)->drm_read_only_fd = fd; } static void drm_lease_device_handle_connector(void *data, struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1, struct wp_drm_lease_connector_v1 *connector) { struct xwl_drm_lease_device *lease_device = data; struct xwl_screen *xwl_screen = lease_device->xwl_screen; struct xwl_output *xwl_output; char name[MAX_OUTPUT_NAME] = { 0 }; xwl_output = calloc(1, sizeof *xwl_output); if (xwl_output == NULL) { ErrorF("%s ENOMEM\n", __func__); return; } xwl_output->lease_device = lease_device; xwl_output->xwl_screen = xwl_screen; xwl_output->lease_connector = connector; xwl_output->randr_crtc = RRCrtcCreate(xwl_screen->screen, xwl_output); if (!xwl_output->randr_crtc) { ErrorF("Failed creating RandR CRTC\n"); goto err; } RRCrtcSetRotations(xwl_output->randr_crtc, ALL_ROTATIONS); xwl_output->randr_output = RROutputCreate(xwl_screen->screen, name, MAX_OUTPUT_NAME, xwl_output); snprintf(name, MAX_OUTPUT_NAME, "XWAYLAND%d", xwl_screen_get_next_output_serial(xwl_screen)); xwl_output_set_name(xwl_output, name); if (!xwl_output->randr_output) { ErrorF("Failed creating RandR Output\n"); goto err; } RRCrtcGammaSetSize(xwl_output->randr_crtc, 256); RROutputSetCrtcs(xwl_output->randr_output, &xwl_output->randr_crtc, 1); RROutputSetConnection(xwl_output->randr_output, RR_Connected); RROutputSetNonDesktop(xwl_output->randr_output, TRUE); xwl_output->randr_output->devPrivate = xwl_output; wp_drm_lease_connector_v1_add_listener(connector, &lease_connector_listener, xwl_output); xorg_list_append(&xwl_output->link, &xwl_screen->output_list); return; err: if (xwl_output->randr_crtc) RRCrtcDestroy(xwl_output->randr_crtc); free(xwl_output); } static void drm_lease_device_handle_released(void *data, struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1) { struct xwl_drm_lease_device *lease_device = data; xwl_screen_destroy_drm_lease_device(lease_device->xwl_screen, wp_drm_lease_device_v1); } static void drm_lease_device_handle_done(void *data, struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1) { /* This space is deliberately left blank */ } static const struct wp_drm_lease_device_v1_listener drm_lease_device_listener = { .drm_fd = drm_lease_device_handle_drm_fd, .connector = drm_lease_device_handle_connector, .released = drm_lease_device_handle_released, .done = drm_lease_device_handle_done, }; void xwl_screen_add_drm_lease_device(struct xwl_screen *xwl_screen, uint32_t id) { struct wp_drm_lease_device_v1 *lease_device = wl_registry_bind( xwl_screen->registry, id, &wp_drm_lease_device_v1_interface, 1); struct xwl_drm_lease_device *device_data = malloc(sizeof(struct xwl_drm_lease_device)); device_data->drm_lease_device = lease_device; device_data->xwl_screen = xwl_screen; device_data->drm_read_only_fd = -1; device_data->id = id; xorg_list_add(&device_data->link, &xwl_screen->drm_lease_devices); wp_drm_lease_device_v1_add_listener(lease_device, &drm_lease_device_listener, device_data); } void xwl_screen_destroy_drm_lease_device(struct xwl_screen *xwl_screen, struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1) { struct xwl_drm_lease_device *device_data; xorg_list_for_each_entry(device_data, &xwl_screen->drm_lease_devices, link) { if (device_data->drm_lease_device == wp_drm_lease_device_v1) { wp_drm_lease_device_v1_destroy(wp_drm_lease_device_v1); xorg_list_del(&device_data->link); if (device_data->drm_read_only_fd >= 0) close(device_data->drm_read_only_fd); free(device_data); return; } } }