summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith Packard <keithp@keithp.com>2018-01-18 16:30:25 -0800
committerKeith Packard <keithp@keithp.com>2018-01-18 16:30:25 -0800
commitd08a95cbbeb909db3637fe0cfaa70d54fa8ed218 (patch)
treea083f6a87bfda878b21247c36fcdcd21b99b8b9f
First version
-rw-r--r--Makefile.am41
-rwxr-xr-xautogen.sh14
-rw-r--r--configure.ac48
-rw-r--r--xlease.c434
4 files changed, 537 insertions, 0 deletions
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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <xcb/randr.h>
+
+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=<display>] [--output=<output>] [--env=<name>] [--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);
+}