From d08a95cbbeb909db3637fe0cfaa70d54fa8ed218 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Thu, 18 Jan 2018 16:30:25 -0800 Subject: First version --- Makefile.am | 41 ++++++ autogen.sh | 14 ++ configure.ac | 48 +++++++ xlease.c | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 537 insertions(+) create mode 100644 Makefile.am create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 xlease.c diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..697f89c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,41 @@ +# +# Copyright © 2018 Keith Packard +# +# 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. +# + +bin_PROGRAMS = xlease + +AM_CFLAGS = $(XLEASE_CFLAGS) +xlease_LDADD = $(XLEASE_LIBS) + +xlease_SOURCES = \ + xlease.c + +MAINTAINERCLEANFILES = ChangeLog INSTALL + +.PHONY: ChangeLog INSTALL + +INSTALL: + $(INSTALL_CMD) + +ChangeLog: + $(CHANGELOG_CMD) + +dist-hook: ChangeLog INSTALL diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..fc34bd5 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` +cd $srcdir + +autoreconf -v --install || exit 1 +cd $ORIGDIR || exit $? + +if test -z "$NOCONFIGURE"; then + $srcdir/configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..783531e --- /dev/null +++ b/configure.ac @@ -0,0 +1,48 @@ +dnl +dnl Copyright © 2018 Keith Packard +dnl +dnl Permission to use, copy, modify, distribute, and sell this software and its +dnl documentation for any purpose is hereby granted without fee, provided that +dnl the above copyright notice appear in all copies and that both that copyright +dnl notice and this permission notice appear in supporting documentation, and +dnl that the name of the copyright holders not be used in advertising or +dnl publicity pertaining to distribution of the software without specific, +dnl written prior permission. The copyright holders make no representations +dnl about the suitability of this software for any purpose. It is provided "as +dnl is" without express or implied warranty. +dnl +dnl THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +dnl INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +dnl EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR +dnl CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +dnl DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +dnl TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +dnl OF THIS SOFTWARE. +dnl +dnl Process this file with autoconf to create configure. + +# Initialize Autoconf +AC_PREREQ([2.60]) +AC_INIT([xlease], [1.0], + [https://bugs.freedesktop.org/enter_bug.cgi?product=xorg], [xlease]) +AC_CONFIG_SRCDIR([Makefile.am]) +AC_CONFIG_HEADERS([config.h]) + +# Initialize Automake +AM_INIT_AUTOMAKE([foreign dist-bzip2]) + +# Require X.Org macros 1.8 or later for MAN_SUBSTS set by XORG_MANPAGE_SECTIONS +m4_ifndef([XORG_MACROS_VERSION], + [m4_fatal([must install xorg-macros 1.8 or later before running autoconf/autogen])]) +XORG_MACROS_VERSION(1.8) +XORG_DEFAULT_OPTIONS + +# Checks for pkg-config packages +PKG_CHECK_MODULES(XLEASE, [randrproto >= 1.6.0] xcb xcb-randr) + +AC_CONFIG_FILES([ + Makefile + ]) + +AC_OUTPUT + diff --git a/xlease.c b/xlease.c new file mode 100644 index 0000000..79c48ec --- /dev/null +++ b/xlease.c @@ -0,0 +1,434 @@ +/* + * Copyright © 2018 Keith Packard + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *default_x_server[] = { + "X", + NULL +}; + +#define DEFAULT_ENV "XF86_VIDEO_MODESETTING_FD" + +/* Lease an output from X and start an X server running on it */ + +/* Information about one lease */ +typedef struct { + int fd; + + /* X resources */ + xcb_randr_output_t randr_output; + xcb_randr_crtc_t randr_crtc; + xcb_randr_lease_t randr_lease; + xcb_randr_mode_info_t randr_mode; + +} lease_t; + +/* Global information */ +typedef struct { + char *display; + char *output_name; + char **xserver; + char *env; + char *shell; + int verbose; + xcb_connection_t *conn; + xcb_window_t root; + xcb_timestamp_t config_timestamp; + uint8_t event_base; + uint8_t error_base; + xcb_randr_get_screen_resources_reply_t *gsr_r; +} app_t; + +/* + * Create a lease for the desired output, select a crtc if none was + * provided. + */ +static int +make_randr_lease(app_t *app, lease_t *lease, xcb_randr_output_t output, xcb_randr_crtc_t crtc, bool whinge, xcb_generic_error_t **error) +{ + /* Pick a crtc if none was provided */ + if (crtc == XCB_NONE) { + xcb_randr_crtc_t *rc = xcb_randr_get_screen_resources_crtcs(app->gsr_r); + + xcb_randr_crtc_t idle_crtc = XCB_NONE; + xcb_randr_crtc_t active_crtc = XCB_NONE; + + /* Find either a crtc already connected to the desired output or idle */ + for (int c = 0; active_crtc == XCB_NONE && c < app->gsr_r->num_crtcs; c++) { + xcb_randr_get_crtc_info_cookie_t gci_c = xcb_randr_get_crtc_info(app->conn, rc[c], app->gsr_r->config_timestamp); + + xcb_randr_get_crtc_info_reply_t *gci_r = xcb_randr_get_crtc_info_reply(app->conn, gci_c, NULL); + + if (gci_r->mode) { + int num_outputs = xcb_randr_get_crtc_info_outputs_length(gci_r); + xcb_randr_output_t *outputs = xcb_randr_get_crtc_info_outputs(gci_r); + for (int o = 0; o < num_outputs; o++) + if (outputs[o] == output && num_outputs == 1) { + active_crtc = rc[c]; + break; + } + + } else if (idle_crtc == 0) { + int num_possible = xcb_randr_get_crtc_info_possible_length(gci_r); + xcb_randr_output_t *possible = xcb_randr_get_crtc_info_possible(gci_r); + for (int p = 0; p < num_possible; p++) + if (possible[p] == output) { + idle_crtc = rc[c]; + break; + } + } + + free(gci_r); + } + if (active_crtc) + crtc = active_crtc; + else + crtc = idle_crtc; + + } + + if (crtc == XCB_NONE) { + if (whinge) + printf("\t\tcannot find usable CRTC\n"); + return 0; + } + + /* + * Create a RandR lease + */ + + xcb_randr_lease_t randr_lease = xcb_generate_id(app->conn); + + xcb_randr_create_lease_cookie_t cl_c = xcb_randr_create_lease(app->conn, + app->root, + randr_lease, + 1, + 1, + &crtc, + &output); + xcb_randr_create_lease_reply_t *cl_r = xcb_randr_create_lease_reply(app->conn, cl_c, error); + if (!cl_r) { + if (whinge) + printf ("create lease failed\n"); + return 0; + } + + int fd = -1; + if (cl_r->nfd > 0) { + int *rcl_f = xcb_randr_create_lease_reply_fds(app->conn, cl_r); + + fd = rcl_f[0]; + } + free (cl_r); + if (fd < 0) { + if (whinge) + printf("\t\tLease returned invalid fd\n"); + return 0; + } + + lease->fd = fd; + lease->randr_output = output; + lease->randr_crtc = crtc; + lease->randr_lease = randr_lease; + + return 1; +} + +static void +init_lease(lease_t *lease) +{ + memset(lease, 0, sizeof (*lease)); + lease->fd = -1; +} + +static void +init_app(app_t *app) +{ + memset(app, 0, sizeof (*app)); +} + +static int +make_lease(app_t *app, lease_t *lease, xcb_randr_output_t output) +{ + init_lease(lease); + + if (!make_randr_lease(app, lease, output, XCB_NONE, true, NULL)) + return 0; + + return 1; +} + +static void +free_randr_lease(app_t *app, lease_t *lease) +{ + if (lease->randr_lease) { + xcb_randr_free_lease(app->conn, lease->randr_lease, 0); + free(xcb_get_input_focus_reply(app->conn, xcb_get_input_focus(app->conn), NULL)); + lease->randr_lease = 0; + } +} + +static void +close_kernel_lease(app_t *app, lease_t *lease) +{ + if (lease->fd >= 0) { + close(lease->fd); + lease->fd = -1; + } +} + +static void +close_lease(app_t *app, lease_t *lease) +{ + close_kernel_lease(app, lease); + free_randr_lease(app, lease); +} + +static void +describe_error(app_t *app, xcb_generic_error_t *error) +{ + if (error->error_code <= XCB_IMPLEMENTATION) { + fprintf(stderr, "Core error %d\n", error->error_code); + } else { + switch (error->error_code - app->error_base) { + case XCB_RANDR_BAD_OUTPUT: + fprintf(stderr, "bad output\n"); break; + case XCB_RANDR_BAD_CRTC: + fprintf(stderr, "bad crtc\n"); break; + case XCB_RANDR_BAD_MODE: + fprintf(stderr, "bad mode\n"); break; + case XCB_RANDR_BAD_PROVIDER: + fprintf(stderr, "bad provider\n"); break; + default: + fprintf(stderr, "error code %d\n", error->error_code); + } + } +} + +static int +app_setup(app_t *app) +{ + int screen; + + app->conn = xcb_connect(app->display, &screen); + + if (!app->conn) { + fprintf(stderr, "Cannot connect to X server\n"); + return 0; + } + + const xcb_setup_t *setup = xcb_get_setup(app->conn); + + /* + * Find our root window + */ + xcb_screen_iterator_t iter; + for (iter = xcb_setup_roots_iterator(setup); iter.rem; xcb_screen_next(&iter)) { + if (screen == 0) { + app->root = iter.data->root; + break; + } + --screen; + } + + const xcb_query_extension_reply_t *qer = xcb_get_extension_data(app->conn, &xcb_randr_id); + + if (!qer) { + fprintf(stderr, "Cannot get randr extension data\n"); + xcb_disconnect(app->conn); + return 0; + } + + app->error_base = qer->first_error; + app->event_base = qer->first_event; + + return 1; +} + +static int +app_get_randr_resources(app_t *app) +{ + if (app->gsr_r) { + free(app->gsr_r); + app->gsr_r = NULL; + } + + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(app->conn, app->root); + + xcb_generic_error_t *error; + + app->gsr_r = xcb_randr_get_screen_resources_reply(app->conn, gsr_c, &error); + + if (!app->gsr_r) { + printf("\t\tget_screen_resources failed\n"); + if (error) + describe_error(app, error); + return 0; + } + + app->config_timestamp = app->gsr_r->config_timestamp; + + return 1; +} + +static void +app_fini(app_t *app) +{ + free(app->gsr_r); + app->gsr_r = NULL; + + xcb_disconnect(app->conn); + app->conn = NULL; +} + +static const struct option options[] = { + { .name = "output", .has_arg = 1, .val = 'o' }, + { .name = "display", .has_arg = 1, .val = 'd' }, + { .name = "help", .has_arg = 0, .val = '?' }, + { .name = "verbose", .has_arg = 0, .val = 'v' }, + { .name = "env", .has_arg = 1, .val = 'e' }, + { 0 }, +}; + +static void usage(char *program, int code) +{ + fprintf(stderr, "usage: %s [--display=] [--output=] [--env=] [--verbose] [--help] {X server ...}\n", program); + exit(code); +} + +int main (int argc, char **argv) +{ + app_t app; + int c; + + init_app(&app); + + app.xserver = default_x_server; + app.env = DEFAULT_ENV; + + while ((c = getopt_long(argc, argv, "?vo:d:x:s:", options, NULL)) != -1) { + switch (c) { + case 'v': + app.verbose++; + break; + case 'd': + app.display = optarg; + break; + case 'o': + app.output_name = optarg; + break; + case 'e': + app.env = optarg; + break; + case '?': + usage(argv[0], 0); + break; + default: + usage(argv[0], 1); + break; + } + } + + if (optind < argc) + app.xserver = &argv[optind]; + + if (!app.output_name) { + fprintf(stderr, "No output specified\n"); + exit(1); + } + + if (!app_setup(&app)) + exit(1); + + if (!app_get_randr_resources(&app)) + exit(1); + + xcb_randr_output_t *ro = xcb_randr_get_screen_resources_outputs(app.gsr_r); + int num_outputs = app.gsr_r->num_outputs; + + int o; + + xcb_randr_output_t output = XCB_NONE; + + for (o = 0; o < num_outputs; o++) { + + xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(app.conn, ro[o], app.config_timestamp); + + xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(app.conn, goi_c, NULL); + + uint8_t *output_name = xcb_randr_get_output_info_name(goi_r); + int output_name_length = xcb_randr_get_output_info_name_length(goi_r); + + if (output_name_length == strlen(app.output_name) && + memcmp(output_name, app.output_name, output_name_length) == 0) + { + output = ro[o]; + break; + } + } + + if (!output) { + fprintf(stderr, "%s: no such output\n", app.output_name); + exit(1); + } + + lease_t lease; + + init_lease(&lease); + + if (!make_lease(&app, &lease, output)) { + fprintf(stderr, "%s: lease create failed\n", app.output_name); + exit(1); + } + + xcb_disconnect(app.conn); + + char *fd_string; + + if (asprintf(&fd_string, "%d", lease.fd) <= 0) { + perror("asprintf"); + exit(1); + } + + if (setenv(app.env, fd_string, 1) < 0) { + perror("setenv"); + exit(1); + } + + if (execvp(app.xserver[0], app.xserver)) { + perror(app.xserver[0]); + exit(1); + } + + exit(0); +} -- cgit v1.2.3