/* * 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 const char *default_x_server[] = { "X", "-seat", "seat1", ":1", NULL }; #define DEFAULT_FD_ARG "-masterfd" /* 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 *fd_arg; 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; } #if 0 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); } #endif 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; } #if 0 static void app_fini(app_t *app) { free(app->gsr_r); app->gsr_r = NULL; xcb_disconnect(app->conn); app->conn = NULL; } #endif static const struct option options[] = { { .name = "display", .has_arg = 1, .val = 'd' }, { .name = "output", .has_arg = 1, .val = 'o' }, { .name = "fd-arg", .has_arg = 1, .val = 'f' }, { .name = "help", .has_arg = 0, .val = '?' }, { .name = "verbose", .has_arg = 0, .val = 'v' }, { 0 }, }; static void usage(char *program, int code) { fprintf(stderr, "usage: %s [--display=] [--output=] [--fd-arg=<--name>] [--verbose] [--help] {command args ...}\n", program); exit(code); } int main (int argc, char **argv) { app_t app; int c; init_app(&app); app.xserver = (char **) default_x_server; app.fd_arg = (char *) DEFAULT_FD_ARG; while ((c = getopt_long(argc, argv, "?vo:d:f:", options, NULL)) != -1) { switch (c) { case 'v': app.verbose++; break; case 'd': app.display = optarg; break; case 'o': app.output_name = optarg; break; case 'f': app.fd_arg = 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); } int xserver_argc = 0; for (xserver_argc = 0; app.xserver[xserver_argc]; xserver_argc++); char **xserver = calloc((xserver_argc + 5), sizeof (char *)); if (!xserver) { perror("calloc"); exit(1); } memcpy(xserver, app.xserver, xserver_argc * sizeof (char *)); xserver[xserver_argc++] = app.fd_arg; xserver[xserver_argc++] = fd_string; xserver[xserver_argc++] = NULL; if (execvp(xserver[0], xserver)) { perror(xserver[0]); exit(1); } exit(0); }