487 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * 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 <xwayland-config.h>
 | |
| 
 | |
| #include "os/client_priv.h"
 | |
| 
 | |
| #ifdef WITH_LIBDRM
 | |
| #include <xf86drm.h>
 | |
| #include <xf86drmMode.h>
 | |
| #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;
 | |
|         }
 | |
|     }
 | |
| }
 |