452 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			452 lines
		
	
	
		
			15 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>
 | 
						|
 | 
						|
#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;
 | 
						|
        output->lease = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    for (i = 0; i < rrLease->numOutputs; ++i) {
 | 
						|
        output = rrLease->outputs[i]->devPrivate;
 | 
						|
        if (!output || !output->lease_connector || output->lease) {
 | 
						|
            return BadValue;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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)
 | 
						|
{
 | 
						|
    /* This space is deliberately left blank */
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
{
 | 
						|
    xwl_output_remove(data);
 | 
						|
}
 | 
						|
 | 
						|
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[256];
 | 
						|
 | 
						|
    xwl_output = calloc(1, sizeof *xwl_output);
 | 
						|
    if (xwl_output == NULL) {
 | 
						|
        ErrorF("%s ENOMEM\n", __func__);
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    snprintf(name, sizeof name, "XWAYLAND%d",
 | 
						|
             xwl_screen_get_next_output_serial(xwl_screen));
 | 
						|
 | 
						|
    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, strlen(name), xwl_output);
 | 
						|
    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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |