diff --git a/Makefile.am b/Makefile.am index 5772a02..4b8e404 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,16 +19,21 @@ # DATA OR PROFITS, WHETHER IN AN ACTION # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -bin_PROGRAMS = xrandr +bin_PROGRAMS = xrandr dispswtch AM_CFLAGS = $(XRANDR_CFLAGS) xrandr_LDADD = $(XRANDR_LIBS) +dispswtch_LDADD = $(XRANDR_LIBS) xrandr_SOURCES = \ xrandr.c +dispswtch_SOURCES = \ + dispswtch.c + appman_PRE = \ - xrandr.man + xrandr.man \ + dispswtch.man appmandir = $(APP_MAN_DIR) diff --git a/dispswtch.c b/dispswtch.c new file mode 100755 index 0000000..a53af6b --- /dev/null +++ b/dispswtch.c @@ -0,0 +1,1734 @@ +/* + * Copyright © 2001 Keith Packard, member of The XFree86 Project, Inc. + * Copyright © 2002 Hewlett Packard Company, Inc. + * Copyright © 2006 Intel Corporation + * Copyright © 2006 Sun Microsystems, Inc. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sys/stat.h" + +static char *program_name; +static Display *dpy = NULL; +static Window root; +static int screen; +static Bool notwin = False; +static Bool verbose = False; +static Bool testrun = False; + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [options]\n", program_name); + fprintf(stderr, " where options are:\n"); + fprintf(stderr, " -display or -d \n"); + fprintf(stderr, " -key or -k \n"); + fprintf(stderr, " -mod or -m \n"); + fprintf(stderr, " --help\n"); + fprintf(stderr, " --notwin\n"); + fprintf(stderr, " --verbose\n"); + fprintf(stderr, " --testrun\n"); + exit(1); + /*NOTREACHED*/ +} + +static void +fatal (const char *format, ...) +{ + va_list ap; + + va_start (ap, format); + fprintf (stderr, "%s exiting: ", program_name); + vfprintf (stderr, format, ap); + va_end (ap); + exit (1); + /*NOTREACHED*/ +} + +typedef enum _relation { + left_of, right_of, above, below, same_as, +} relation_t; + +typedef enum _changes { + changes_none = 0, + changes_crtc = (1 << 0), + changes_mode = (1 << 1), + changes_relation = (1 << 2), + changes_position = (1 << 3), +} changes_t; + +typedef enum _name_kind { + name_none = 0, + name_string = (1 << 0), + name_xid = (1 << 1), + name_index = (1 << 2), +} name_kind_t; + +typedef struct { + name_kind_t kind; + char *string; + XID xid; + int index; +} name_t; + +typedef struct _crtc crtc_t; +typedef struct _output output_t; + +struct _crtc { + name_t crtc; + XRRCrtcInfo *crtc_info; + + XRRModeInfo *mode_info; + int x; + int y; + Rotation rotation; + output_t **outputs; + int noutput; +}; + +struct _output { + struct _output *next; + + changes_t changes; + + name_t output; + XRROutputInfo *output_info; + + name_t crtc; + crtc_t *crtc_info; + crtc_t *current_crtc_info; + + name_t mode; + float refresh; + XRRModeInfo *mode_info; + + name_t addmode; + + relation_t relation; + struct _output *relative_to; + + int x, y; + int xsave, ysave; /* positions saved when in single display */ + Rotation rotation; +}; + +static output_t *outputs = NULL; +static output_t **outputs_tail = &outputs; +static crtc_t *crtcs = NULL; +static int num_crtcs; +static XRRScreenResources *res = NULL; +static int fb_width = 0, fb_height = 0; +static int fb_width_mm = 0, fb_height_mm = 0; +static float dpi = 0; +static Bool dryrun = False; +static int minWidth, maxWidth, minHeight, maxHeight; + +#define MAX_OUTPUT 3 +#define MAX_MODIFIERS 7 +#define DISP_SWITCH_NEEDS_PROBE "/tmp/.disp_switch_needs_probe" + +typedef struct _con_output con_output_t; + +struct _con_output { + output_t *output; + XRRModeInfo **smodes; + int nsmodes; + Bool on; +}; + +typedef struct _mod_key_table mod_key_table_t; + +struct _mod_key_table { + char *modname; + int mod; +}; + +static con_output_t con_outputs[MAX_OUTPUT]; +static con_output_t dis_con_outputs[MAX_OUTPUT]; +static XRRModeInfo *start_mode[MAX_OUTPUT]; +static XRRModeInfo *new_mode[MAX_OUTPUT]; +static int start; +static int ncon; +static int dis_ncon; +static FILE *fd; +static int pid = 0; +static Bool do_not_switch = False; + +static mod_key_table_t mod_key_table [MAX_MODIFIERS] = { + {"shift", ShiftMask}, + {"lock", LockMask}, + {"control", ControlMask}, + {"mod1", Mod1Mask}, + {"mod2", Mod2Mask}, + {"mod3", Mod3Mask}, + {"mod4", Mod4Mask} +}; + +static int +mode_height (XRRModeInfo *mode_info, Rotation rotation) +{ + switch (rotation & 0xf) { + case RR_Rotate_0: + case RR_Rotate_180: + return mode_info->height; + case RR_Rotate_90: + case RR_Rotate_270: + return mode_info->width; + default: + return 0; + } +} + +static int +mode_width (XRRModeInfo *mode_info, Rotation rotation) +{ + switch (rotation & 0xf) { + case RR_Rotate_0: + case RR_Rotate_180: + return mode_info->width; + case RR_Rotate_90: + case RR_Rotate_270: + return mode_info->height; + default: + return 0; + } +} + +/* v refresh frequency in Hz */ +static float +mode_refresh (XRRModeInfo *mode_info) +{ + float rate; + + if (mode_info->hTotal && mode_info->vTotal) + rate = ((float) mode_info->dotClock / + ((float) mode_info->hTotal * (float) mode_info->vTotal)); + else + rate = 0; + return rate; +} + +/* h sync frequency in Hz */ +static float +mode_hsync (XRRModeInfo *mode_info) +{ + float rate; + + if (mode_info->hTotal) + rate = (float) mode_info->dotClock / (float) mode_info->hTotal; + else + rate = 0; + return rate; +} + +static void +init_name (name_t *name) +{ + name->kind = name_none; +} + +static void +set_name_string (name_t *name, char *string) +{ + name->kind |= name_string; + name->string = string; +} + +static void +set_name_xid (name_t *name, XID xid) +{ + name->kind |= name_xid; + name->xid = xid; +} + +static void +set_name_index (name_t *name, int index) +{ + name->kind |= name_index; + name->index = index; +} + +static void +set_name_all (name_t *name, name_t *old) +{ + if (old->kind & name_xid) + name->xid = old->xid; + if (old->kind & name_string) + name->string = old->string; + if (old->kind & name_index) + name->index = old->index; + name->kind |= old->kind; +} + +static void +set_name (name_t *name, char *string, name_kind_t valid) +{ + XID xid; + int index; + + if ((valid & name_xid) && sscanf (string, "0x%x", &xid) == 1) + set_name_xid (name, xid); + else if ((valid & name_index) && sscanf (string, "%d", &index) == 1) + set_name_index (name, index); + else if (valid & name_string) + set_name_string (name, string); + else + usage (); +} + +static output_t * +add_output (void) +{ + output_t *output = calloc (1, sizeof (output_t)); + + if (!output) + fatal ("out of memory"); + output->next = NULL; + *outputs_tail = output; + outputs_tail = &output->next; + return output; +} + +static output_t * +find_output (name_t *name) +{ + output_t *output; + + for (output = outputs; output; output = output->next) + { + name_kind_t common = name->kind & output->output.kind; + + if ((common & name_xid) && name->xid == output->output.xid) + break; + if ((common & name_string) && !strcmp (name->string, output->output.string)) + break; + if ((common & name_index) && name->index == output->output.index) + break; + } + return output; +} + +static output_t * +find_output_by_xid (RROutput output) +{ + name_t output_name; + + init_name (&output_name); + set_name_xid (&output_name, output); + return find_output (&output_name); +} + +static crtc_t * +find_crtc (name_t *name) +{ + int c; + crtc_t *crtc = NULL; + + for (c = 0; c < num_crtcs; c++) + { + name_kind_t common; + + crtc = &crtcs[c]; + common = name->kind & crtc->crtc.kind; + + if ((common & name_xid) && name->xid == crtc->crtc.xid) + break; + if ((common & name_string) && !strcmp (name->string, crtc->crtc.string)) + break; + if ((common & name_index) && name->index == crtc->crtc.index) + break; + crtc = NULL; + } + return crtc; +} + +static crtc_t * +find_crtc_by_xid (RRCrtc crtc) +{ + name_t crtc_name; + + init_name (&crtc_name); + set_name_xid (&crtc_name, crtc); + return find_crtc (&crtc_name); +} + +static XRRModeInfo * +find_mode (name_t *name) +{ + int m; + XRRModeInfo *best = NULL; + float bestDist = 0; + + for (m = 0; m < res->nmode; m++) + { + XRRModeInfo *mode = &res->modes[m]; + if ((name->kind & name_xid) && name->xid == mode->id) + { + best = mode; + break; + } + } + return best; +} + +static XRRModeInfo * +find_mode_by_xid (RRMode mode) +{ + name_t mode_name; + + init_name (&mode_name); + set_name_xid (&mode_name, mode); + return find_mode (&mode_name); +} + +static void +set_output_info (output_t *output, RROutput xid, XRROutputInfo *output_info) +{ + /* sanity check output info */ + if (output_info->connection != RR_Disconnected && !output_info->nmode) + fatal ("Output %s is not disconnected but has no modes\n", + output_info->name); + + /* set output name and info */ + if (!(output->output.kind & name_xid)) + set_name_xid (&output->output, xid); + if (!(output->output.kind & name_string)) + set_name_string (&output->output, output_info->name); + output->output_info = output_info; + + /* set position */ + if (output->crtc_info) { + output->xsave = output->crtc_info->crtc_info->x; + output->ysave = output->crtc_info->crtc_info->y; + } else { + output->xsave = 0; + output->ysave = 0; + } + + /* set rotation */ + output->rotation &= ~0xf; + if (output->crtc_info) + output->rotation |= (output->crtc_info->crtc_info->rotation & 0xf); + else + output->rotation = RR_Rotate_0; + output->rotation &= ~(RR_Reflect_X|RR_Reflect_Y); + if (output->crtc_info) + output->rotation |= (output->crtc_info->crtc_info->rotation & + (RR_Reflect_X|RR_Reflect_Y)); +} + +static void +get_crtcs (void) +{ + int c; + + num_crtcs = res->ncrtc; + if (crtcs) + { + for (c = 0; c < res->ncrtc; c++) + { + if (crtcs[c].crtc_info) + XRRFreeCrtcInfo (crtcs[c].crtc_info); + } + free (crtcs); + crtcs = NULL; + } + + crtcs = calloc (num_crtcs, sizeof (crtc_t)); + if (!crtcs) fatal ("out of memory"); + + for (c = 0; c < res->ncrtc; c++) + { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo (dpy, res, res->crtcs[c]); + set_name_xid (&crtcs[c].crtc, res->crtcs[c]); + set_name_index (&crtcs[c].crtc, c); + if (!crtc_info) fatal ("could not get crtc 0x%x information", res->crtcs[c]); + crtcs[c].crtc_info = crtc_info; + if (crtc_info->mode == None) + { + crtcs[c].mode_info = NULL; + crtcs[c].x = 0; + crtcs[c].y = 0; + crtcs[c].rotation = RR_Rotate_0; + } + } +} + +static void +crtc_add_output (crtc_t *crtc, output_t *output) +{ + if (crtc->outputs) + crtc->outputs = realloc (crtc->outputs, (crtc->noutput + 1) * sizeof (output_t *)); + else + { + crtc->outputs = calloc (1, sizeof (output_t *)); + crtc->x = output->x; + crtc->y = output->y; + crtc->rotation = output->rotation; + crtc->mode_info = output->mode_info; + } + if (!crtc->outputs) fatal ("out of memory"); + crtc->outputs[crtc->noutput++] = output; +} + +static void +set_crtcs (void) +{ + output_t *output; + int i; + + for (i = 0; i < ncon; i++) { + output = con_outputs[i].output; + if (!output->mode_info) continue; + if (output->crtc_info) + crtc_add_output (output->crtc_info, output); + } +} + +static void +reset_crtcs (void) +{ + output_t *output; + + for (output = outputs; output; output = output->next) + { + if ((output->crtc_info) && (output->crtc_info->outputs)) { + free (output->crtc_info->outputs); + output->crtc_info = NULL; + } + } +} + +static Status +crtc_disable (crtc_t *crtc) +{ + if (verbose) + fprintf (stderr, "crtc %d (%x) : disable\n", crtc->crtc.index, crtc->crtc.xid); + + if (dryrun) + return RRSetConfigSuccess; + + return XRRSetCrtcConfig (dpy, res, crtc->crtc.xid, CurrentTime, + 0, 0, None, RR_Rotate_0, NULL, 0); +} + +static Status +crtc_revert (crtc_t *crtc) +{ + XRRCrtcInfo *crtc_info = crtc->crtc_info; + + if (verbose) + fprintf (stderr, "crtc %d: revert\n", crtc->crtc.index); + + if (dryrun) + return RRSetConfigSuccess; + return XRRSetCrtcConfig (dpy, res, crtc->crtc.xid, CurrentTime, + crtc_info->x, crtc_info->y, + crtc_info->mode, crtc_info->rotation, + crtc_info->outputs, crtc_info->noutput); +} + +static Status +crtc_apply (crtc_t *crtc) +{ + RROutput *rr_outputs; + int o; + Status s; + RRMode mode = None; + + if (!crtc->mode_info) + return RRSetConfigSuccess; + + rr_outputs = calloc (crtc->noutput, sizeof (RROutput)); + if (!rr_outputs) + return BadAlloc; + for (o = 0; o < crtc->noutput; o++) + rr_outputs[o] = crtc->outputs[o]->output.xid; + mode = crtc->mode_info->id; + if (verbose) { + fprintf (stderr, "crtc %d (%x) : %12s %6.1f +%d+%d", crtc->crtc.index, + crtc->crtc.xid, + crtc->mode_info->name, mode_refresh (crtc->mode_info), + crtc->x, crtc->y); + for (o = 0; o < crtc->noutput; o++) + fprintf (stderr, " \"%s\"", crtc->outputs[o]->output.string); + fprintf (stderr, "\n"); + } + if (dryrun) + s = RRSetConfigSuccess; + else + s = XRRSetCrtcConfig (dpy, res, crtc->crtc.xid, CurrentTime, + crtc->x, crtc->y, mode, crtc->rotation, + rr_outputs, crtc->noutput); + free (rr_outputs); + return s; +} + +static void +screen_revert (void) +{ + if (verbose) + fprintf (stderr, "screen %d: revert\n", screen); + + if (dryrun) + return; + XRRSetScreenSize (dpy, root, + DisplayWidth (dpy, screen), + DisplayHeight (dpy, screen), + DisplayWidthMM (dpy, screen), + DisplayHeightMM (dpy, screen)); +} + +static void +screen_apply (void) +{ + if (fb_width == DisplayWidth (dpy, screen) && + fb_height == DisplayHeight (dpy, screen) && + fb_width_mm == DisplayWidthMM (dpy, screen) && + fb_height_mm == DisplayHeightMM (dpy, screen)) + { + return; + } + if (verbose) + fprintf (stderr, "screen %d: %dx%d %dx%d mm %6.2fdpi\n", screen, + fb_width, fb_height, fb_width_mm, fb_height_mm, dpi); + if (dryrun) + return; + XRRSetScreenSize (dpy, root, fb_width, fb_height, + fb_width_mm, fb_height_mm); +} + +static void +revert (void) +{ + int c; + + /* first disable all crtcs */ + for (c = 0; c < res->ncrtc; c++) + crtc_disable (&crtcs[c]); + /* next reset screen size */ + screen_revert (); + /* now restore all crtcs */ + for (c = 0; c < res->ncrtc; c++) + crtc_revert (&crtcs[c]); +} + +/* + * uh-oh, something bad happened in the middle of changing + * the configuration. Revert to the previous configuration + * and bail + */ +static void +panic (Status s, crtc_t *crtc) +{ + int c = crtc->crtc.index; + char *message; + + switch (s) { + case RRSetConfigSuccess: message = "succeeded"; break; + case BadAlloc: message = "out of memory"; break; + case RRSetConfigFailed: message = "failed"; break; + case RRSetConfigInvalidConfigTime: message = "invalid config time"; break; + case RRSetConfigInvalidTime: message = "invalid time"; break; + default: message = "unknown failure"; break; + } + + fprintf (stderr, "%s: Configure crtc %d %s\n", program_name, c, message); + revert (); + exit (1); +} + +void +apply (void) +{ + Status s; + int c; + + /* + * Turn off any crtcs which are to be disabled or which are + * larger than the target size + */ + for (c = 0; c < res->ncrtc; c++) + { + crtc_t *crtc = &crtcs[c]; + XRRCrtcInfo *crtc_info = crtc->crtc_info; + + /* if this crtc is already disabled, skip it + * Note server sets crtc_info->mode (before change) + */ + if (crtc_info->mode == None) + continue; + + /* + * If this crtc is to be left enabled, make + * sure the old size fits then new screen + * When crtc->mode_info is null, the crtc is to be + * disabled. Note set_crtcs () sets crtc->mode_info for + * new mode (to be changed to). + */ + if (crtc->mode_info) + { + XRRModeInfo *old_mode = find_mode_by_xid (crtc_info->mode); + int x, y, w, h; + + if (!old_mode) + panic (RRSetConfigFailed, crtc); + + /* old position and size information */ + x = crtc_info->x; + y = crtc_info->y; + w = mode_width (old_mode, crtc_info->rotation); + h = mode_height (old_mode, crtc_info->rotation); + + /* if it fits, skip it */ + if (x + w <= fb_width && y + h <= fb_height) + continue; + } + s = crtc_disable (crtc); + if (s != RRSetConfigSuccess) + panic (s, crtc); + } + + /* + * Hold the server grabbed while messing with + * the screen so that apps which notice the resize + * event and ask for xinerama information from the server + * receive up-to-date information + */ + XGrabServer (dpy); + + /* + * Set the screen size + */ + screen_apply (); + + /* + * Set crtcs + */ + + for (c = 0; c < res->ncrtc; c++) + { + crtc_t *crtc = &crtcs[c]; + + s = crtc_apply (crtc); + if (s != RRSetConfigSuccess) + panic (s, crtc); + } + /* + * Release the server grab and let all clients + * respond to the updated state + */ + XUngrabServer (dpy); +} + +/* + * Use current output state to complete the output list + */ +void +get_outputs (void) +{ + int o; + + for (o = 0; o < res->noutput; o++) + { + XRROutputInfo *output_info = XRRGetOutputInfo (dpy, res, res->outputs[o]); + output_t *output; + name_t output_name; + + if (!output_info) fatal ("could not get output 0x%x information", res->outputs[o]); + set_name_xid (&output_name, res->outputs[o]); + set_name_index (&output_name, o); + set_name_string (&output_name, output_info->name); + output = find_output (&output_name); + if (!output) + { + output = add_output (); + set_name_all (&output->output, &output_name); + } + + set_output_info (output, res->outputs[o], output_info); + } +} + + +/* + * Test whether 'crtc' can be used for 'output' + */ +Bool +check_crtc_for_output (crtc_t *crtc, output_t *output) +{ + int i; + int l; + output_t *other; + + for (i = 0; i < ncon; i++) + { + other = con_outputs[i].output; + + if (other == output) + continue; + + if (other->mode_info == NULL) + continue; + + if (other->crtc_info != crtc) + continue; + + /* see if the output connected to the crtc can clone to this output */ + for (l = 0; l < output->output_info->nclone; l++) + if (output->output_info->clones[l] == other->output.xid) + break; + /* not on the list, can't clone */ + if (l == output->output_info->nclone) + return False; + } + + if (crtc->noutput) + { + for (i = 0; i < crtc->noutput; i++) + /* Check if the output is to be turned on */ + if (crtc->outputs[i]->mode_info) { + /* make sure the state matches */ + if (crtc->mode_info != output->mode_info) + return False; + if (crtc->x != output->x) + return False; + if (crtc->y != output->y) + return False; + if (crtc->rotation != output->rotation) + return False; + } + } + + return True; +} + +crtc_t * +find_crtc_for_output (output_t *output) +{ + int c; + + for (c = 0; c < output->output_info->ncrtc; c++) + { + crtc_t *crtc; + + crtc = find_crtc_by_xid (output->output_info->crtcs[c]); + if (!crtc) fatal ("cannot find crtc 0x%x\n", output->output_info->crtcs[c]); + + if (check_crtc_for_output (crtc, output)) + return crtc; + } + return NULL; +} + +static void +set_positions (void) +{ + Bool keep_going; + Bool any_set; + int min_x, min_y; + int i; + + for (;;) + { + any_set = False; + keep_going = False; + for (i = 0; i < ncon; i++) + { + output_t *output = con_outputs[i].output; + output_t *relation; + name_t relation_name; + + if (!(output->changes & changes_relation)) continue; + + if (output->mode_info == NULL) continue; + + relation = output->relative_to; + + if (relation->mode_info == NULL) + { + output->x = 0; + output->y = 0; + output->changes |= changes_position; + any_set = True; + continue; + } + /* + * Make sure the dependent object has been set in place + */ + if ((relation->changes & changes_relation) && + !(relation->changes & changes_position)) + { + keep_going = True; + continue; + } + + switch (output->relation) { + case left_of: + output->y = relation->y; + output->x = relation->x - mode_width (output->mode_info, output->rotation); + break; + case right_of: + output->y = relation->y; + output->x = relation->x + mode_width (relation->mode_info, relation->rotation); + break; + case above: + output->x = relation->x; + output->y = relation->y - mode_height (output->mode_info, output->rotation); + break; + case below: + output->x = relation->x; + output->y = relation->y + mode_height (relation->mode_info, relation->rotation); + break; + case same_as: + output->x = relation->x; + output->y = relation->y; + } + output->changes |= changes_position; + relation->changes |= changes_position; + any_set = True; + } + if (!keep_going) + break; + if (!any_set) + fatal ("loop in relative position specifications\n"); + } + + /* + * Now normalize positions so the upper left corner of all outputs is at 0,0 + */ + min_x = 32768; + min_y = 32768; + for (i = 0; i < ncon; i++) + { + output_t *output = con_outputs[i].output; + + if (output->mode_info == NULL) continue; + + if (output->x < min_x) min_x = output->x; + if (output->y < min_y) min_y = output->y; + } + if (min_x || min_y) + { + /* move all outputs */ + for (i = 0; i < ncon; i++) + { + output_t *output = con_outputs[i].output; + + if (output->mode_info == NULL) continue; + + output->x -= min_x; + output->y -= min_y; + output->changes |= changes_position; + } + } +} + +static Bool +set_screen_size (void) +{ + int i; + + for (i = 0; i < ncon; i++) + { + output_t *output = con_outputs[i].output; + XRRModeInfo *mode_info = output->mode_info; + int x, y, w, h; + + if (!mode_info) continue; + + x = output->x; + y = output->y; + w = mode_width (mode_info, output->rotation); + h = mode_height (mode_info, output->rotation); + if (x + w > fb_width) fb_width = x + w; + if (y + h > fb_height) fb_height = y + h; + } + + if (fb_width > maxWidth || fb_height > maxHeight) { + fprintf (stderr, "screen cannot be larger than %dx%d (desired size %dx%d)\n", + maxWidth, maxHeight, fb_width, fb_height); + return False; + } + + if (fb_width < minWidth) fb_width = minWidth; + if (fb_height < minHeight) fb_height = minHeight; + + return True; +} + +void +disable_outputs (output_t *outputs) +{ + while (outputs) + { + outputs->crtc_info = NULL; + outputs = outputs->next; + } +} + +/* + * find the best mapping from output to crtc available + */ +int +pick_crtcs_score (output_t *outputs) +{ + output_t *output; + int best_score; + int my_score; + int score; + crtc_t *best_crtc; + int c; + + if (!outputs) + return 0; + + output = outputs; + outputs = outputs->next; + /* + * Score with this output disabled + */ + output->crtc_info = NULL; + best_score = pick_crtcs_score (outputs); + if (output->mode_info == NULL) + return best_score; + + best_crtc = NULL; + /* + * Now score with this output any valid crtc + */ + for (c = 0; c < output->output_info->ncrtc; c++) + { + crtc_t *crtc; + + crtc = find_crtc_by_xid (output->output_info->crtcs[c]); + if (!crtc) + fatal ("cannot find crtc 0x%x\n", output->output_info->crtcs[c]); + + /* reset crtc allocation for following outputs */ + disable_outputs (outputs); + if (!check_crtc_for_output (crtc, output)) + continue; + + my_score = 1000; + /* slight preference for existing connections */ + if (crtc == output->current_crtc_info) + my_score++; + + output->crtc_info = crtc; + score = my_score + pick_crtcs_score (outputs); + if (score > best_score) + { + best_crtc = crtc; + best_score = score; + } + } + if (output->crtc_info != best_crtc) + output->crtc_info = best_crtc; + /* + * Reset other outputs based on this one using the best crtc + */ + (void) pick_crtcs_score (outputs); + + return best_score; +} + +/* + * Pick crtcs for any changing outputs that don't have one + */ +Bool +pick_crtcs (void) +{ + output_t *output; + int i; + + /* + * First try to match up newly enabled outputs with spare crtcs + */ + for (i = 0; i < ncon; i++) + { + output = con_outputs[i].output; + + if (output->mode_info) + { + if (output->crtc_info) { + if (output->crtc_info->crtc_info->noutput > 0 && + (output->crtc_info->crtc_info->noutput > 1 || + output != find_output_by_xid (output->crtc_info->crtc_info->outputs[0]))) + break; + } else { + output->crtc_info = find_crtc_for_output (output); + if (!output->crtc_info) + break; + else { + if (verbose) + fprintf(stderr, "picked crtc %x for output %d (%s)\n", + output->crtc_info->crtc.xid, i, output->output_info->name); + } + } + } + } + + /* + * Everyone is happy + */ + if (i == ncon) + return True; + /* + * When the simple way fails, see if there is a way + * to swap crtcs around and make things work + */ + for (output = outputs; output; output = output->next) + output->current_crtc_info = output->crtc_info; + pick_crtcs_score (outputs); + for (output = outputs; output; output = output->next) + { + if (output->mode_info && !output->crtc_info) { + if (verbose) + fprintf (stderr, "cannot find crtc for output %s\n", + output->output.string); + return False; + } + } + + return True; +} + +static Bool +probe_and_check_output_changes (void) { + XRRScreenResources *new_res = NULL; + int changed = False; + int i; + + new_res = XRRGetScreenResources (dpy, root); + if (!res) + fatal ("could not get screen resources"); + + if ((new_res->noutput != res->noutput) || (new_res->nmode != res->nmode)) + changed = True; + else { + for (i = 0; i < new_res->noutput; i++) + if (new_res->outputs[i] != res->outputs[i]) { + changed = True; + break; + } + for (i = 0; i < new_res->nmode; i++) + if (new_res->modes[i].id != res->modes[i].id) { + changed = True; + break; + } + } + + if (changed) { + if (res) + XRRFreeScreenResources(res); + res = new_res; + + if (verbose) + fprintf(stderr, "probed: output status changed\n"); + return True; + } + + if (verbose) + fprintf(stderr, "probed: no output status change\n"); + return False; +} + +static void +finish (void) { + int ch_pid; + int fd; + + ch_pid = fork(); + if (ch_pid) { + if (ch_pid != -1) + pid = ch_pid; + remove (DISP_SWITCH_NEEDS_PROBE); + return; + } else { + usleep (5000000); + if ((fd = open(DISP_SWITCH_NEEDS_PROBE, O_RDWR | O_CREAT | O_TRUNC, 0666)) + == -1) + fprintf (stderr, "cannot create DISP_SWITCH_NEEDS_PROBE file\n"); + else + fchmod(fd, 0666); + exit (0); + } +} + +static int +mode_sort (const void *p1, void *p2) +{ + XRRModeInfo *mi1 = * (XRRModeInfo **) p1; + XRRModeInfo *mi2 = * (XRRModeInfo **) p2; + + if ((mi1->width == mi2->width) && (mi1->height == mi2->height)) { + if (mode_refresh(mi1) && mode_refresh(mi2)) { + if (mode_refresh(mi1) < mode_refresh(mi2)) + return 1; + if (mode_refresh(mi1) > mode_refresh(mi2)) + return -1; + } else + return 0; + } + + if ((mi1->width == mi2->width) && (mi1->height < mi2->height)) + return 1; + if ((mi1->width == mi2->width) && (mi1->height > mi2->height)) + return -1; + if (mi1->width < mi2->width) + return 1; + if (mi1->width > mi2->width) + return -1; + + return 0; +} + +static int +output_sort (const void *p1, const void *p2) { + con_output_t co1 = * (con_output_t *) p1; + con_output_t co2 = * (con_output_t *) p2; + int ncrtc1 = co1.output->output_info->ncrtc; + int ncrtc2 = co2.output->output_info->ncrtc; + char *name1 = co1.output->output_info->name; + char *name2 = co2.output->output_info->name; + + if (ncrtc1 == ncrtc2) + return (strcmp(name1, name2)); + if (ncrtc1 < ncrtc2) + return -1; + + return 1; +} + +static Bool +find_common_mode(con_output_t *c0, con_output_t *c1, int *m0, int *m1) { + int i, j; + + *m0 = -1; + *m0 = -1; + if (!c0 ||!c1 || !c0->smodes || !c1->smodes) + return 1; + + for (i = 0; i < c0->nsmodes; i ++) { + for (j = 0; j < c1->nsmodes; j ++) + if ((c0->smodes[i]->width == c1->smodes[j]->width) && + (c0->smodes[i]->height == c1->smodes[j]->height)) { + *m0 = i; *m1 = j; + return 1; + } + } + + return 0; +} + +void +do_init (void) +{ + int i, j; + output_t *output; + + /* Initialize con_outputs array */ + for (i = 0; i < MAX_OUTPUT; i++) { + con_outputs[i].output = NULL; + con_outputs[i].on = False; + start_mode[i] = NULL; + new_mode[i] = NULL; + } + + ncon = 0; + dis_ncon = 0; + get_crtcs (); + get_outputs (); + + for (output = outputs; output; output = output->next) { + XRROutputInfo *output_info = output->output_info; + + if (output_info->connection == RR_Connected) { + con_outputs[ncon].output = output; + con_outputs[ncon].nsmodes = 0; + for (j = 0; j < output_info->nmode; j++) { + XRRModeInfo *rmode = find_mode_by_xid (output_info->modes[j]); + + con_outputs[ncon].smodes = + realloc(con_outputs[ncon].smodes, + (con_outputs[ncon].nsmodes + 1) * sizeof (XRRModeInfo *)); + con_outputs[ncon].smodes[j] = rmode; + con_outputs[ncon].nsmodes ++; + } + + /* Sort the modes */ + qsort((void *) con_outputs[ncon].smodes, + con_outputs[ncon].nsmodes, sizeof(XRRModeInfo *), + (int (*) (const void *, const void *)) mode_sort); + + if (output_info->crtc) { + con_outputs[ncon].on = True; + for (j = 0; j < output_info->ncrtc; j++) { + if (output_info->crtcs[j] == output_info->crtc) + break; + if (j == output_info->ncrtc) { + if (verbose) + fatal ("crtc does not match for output\n"); + } + } + } + else + con_outputs[ncon].on = False; + ncon ++; + } else if (output_info->connection == RR_Disconnected) { + dis_con_outputs[dis_ncon].output = output; + dis_ncon ++; + } + } + + qsort((void **) con_outputs, ncon, + sizeof(con_output_t), (int (*) (const void *, const void *)) output_sort); + + if (verbose) { + fprintf(stderr, "Total connected outputs = %d :\n", ncon); + for (j = 0; j < ncon; j++) { + fprintf(stderr, "%d (%s) : top mode = %s, number of crtcs %d =", j, + con_outputs[j].output->output_info->name, + con_outputs[j].smodes[0]->name, + con_outputs[j].output->output_info->ncrtc); + for (i = 0; i < con_outputs[j].output->output_info->ncrtc; i++) + fprintf(stderr, " %x", con_outputs[j].output->output_info->crtcs[i]); + fprintf(stderr, ", using %x", con_outputs[j].output->output_info->crtc); + fprintf(stderr, "\n"); + } + fprintf(stderr, "Total disconnected outputs = %d :\n", dis_ncon); + for (j = 0; j < dis_ncon; j++) { + fprintf(stderr, "%d (%s) : number of crtcs %d =", j, + dis_con_outputs[j].output->output_info->name, + dis_con_outputs[j].output->output_info->ncrtc); + for (i = 0; i < dis_con_outputs[j].output->output_info->ncrtc; i++) + fprintf(stderr, " %x", dis_con_outputs[j].output->output_info->crtcs[i]); + fprintf(stderr, ", using %x", dis_con_outputs[j].output->output_info->crtc); + fprintf(stderr, "\n"); + } + } + + i = con_outputs[2].on * 4 + con_outputs[1].on * 2 + con_outputs[0].on; + switch (i) { + case 1: + start_mode[0] = con_outputs[0].output->mode_info; + break; + case 2: + start_mode[1] = con_outputs[1].output->mode_info; + break; + case 4: + start_mode[2] = con_outputs[2].output->mode_info; + break; + } + + if ((ncon != 2) || (i <= 3)) + start = i; + + if ((ncon < 2) || (ncon > 3)) { + if (verbose) + fprintf (stderr, "too many or too few connections: %d: can't switch\n", ncon); + do_not_switch = True; + } else + do_not_switch = False; + + return; +} + +static Bool +do_switch (void) +{ + char *display_name = NULL; + int i, j; + int single; + + for (i = 0; i < ncon; i++) + new_mode[i] = NULL; + + for (i = 0; i < ncon; i++) { + output_t *output = con_outputs[i].output; + + output->relation = 0; + output->relative_to = NULL; + if ((ncon == 2) && (!notwin && (start == 3) || (start == 4))) { + if (output->crtc_info) { + /* get current x, y from previous server setting */ + output->x = output->crtc_info->crtc_info->x; + output->y = output->crtc_info->crtc_info->y; + } + } else { + output->x = output->xsave; + output->y = output->ysave; + } + } + + if (ncon == 2) { + if (!notwin) { + if (++start > 5) start = 1; + } + else { + if (++start > 3) start = 1; + } + + if (verbose) + fprintf(stderr, "current state = %d\n", start); + if (start >= 3) { + int m0, m1; + + if (find_common_mode(&con_outputs[0], &con_outputs[1], &m0, &m1)) { + new_mode[0] = con_outputs[0].smodes[m0]; + new_mode[1] = con_outputs[1].smodes[m1]; + } else { + new_mode[0] = con_outputs[0].smodes[0]; + new_mode[1] = con_outputs[1].smodes[0]; + } + } else { + if (start_mode[start -1]) + new_mode[start -1] = start_mode[start -1]; + else { + if (con_outputs[start -1].smodes[0]) + new_mode[start -1] = con_outputs[start-1].smodes[0]; + } + } + } + + if (ncon == 3) { + if (++start > 6) start = 1; + if (verbose) + fprintf(stderr, "current state = %d\n", start); + if ((start == 1) || (start == 2) || (start == 4)) { + single = 1; + i = start >> 1; + j = 0; + } + else { + single = 0; + if (start > 4) + j = 2; + else + j = 1; + if (start > 5) + i = 1; + else + i = 0; + } + + if (single) { + if (start_mode[i]) + new_mode[i] = start_mode[i]; + else + if (con_outputs[i].smodes[0]) + new_mode[i] = con_outputs[i].smodes[0]; + } + else { + int m0, m1; + + if (find_common_mode(&con_outputs[i], &con_outputs[j], &m0, &m1)) { + new_mode[i] = con_outputs[i].smodes[m0]; + new_mode[j] = con_outputs[j].smodes[m1]; + } else { + new_mode[i] = con_outputs[i].smodes[0]; + new_mode[j] = con_outputs[j].smodes[0]; + } + } + } + + /* Set mode */ + for (i = 0; i < ncon; i++) { + output_t *output; + + output = con_outputs[i].output; + if (new_mode[i]) { + if ((!output->mode_info) || (output->mode_info != new_mode[i])) { + output->mode_info = new_mode[i]; + con_outputs[i].on = True; + if (verbose) + fprintf(stderr, "set output %d (%s) to mode %s\n", i, + con_outputs[i].output->output_info->name, + con_outputs[i].output->mode_info->name); + } + } else if (con_outputs[i].on ) { + output->mode_info = NULL; + con_outputs[i].on = False; + if (verbose) + fprintf(stderr, "turn off output %d (%s) \n", + i, con_outputs[i].output->output_info->name); + } + } + + if ((ncon == 2) && (start >= 4)) { + con_outputs[0].output->relative_to = con_outputs[1].output; + if (start == 4) + con_outputs[0].output->relation = left_of; + else if (start == 5) + con_outputs[0].output->relation = right_of; + + con_outputs[0].output->changes = changes_relation; + set_positions(); + } + + if (!set_screen_size ()) + return False; + + + /* reset crtcs before allocation */ + reset_crtcs(); + get_crtcs(); + if (!pick_crtcs()) { + if (verbose) + fprintf(stderr, "pick_crtcs failed\n"); + return True; + } + set_crtcs (); + + if (fb_width_mm == 0 || fb_height_mm == 0) { + if (fb_width != DisplayWidth (dpy, screen) || + fb_height != DisplayHeight (dpy, screen) || dpi != 0.0) { + if (dpi <= 0) + dpi = (25.4 * DisplayHeight (dpy, screen)) / DisplayHeightMM(dpy, screen); + fb_width_mm = (25.4 * fb_width) / dpi; + fb_height_mm = (25.4 * fb_height) / dpi; + } else { + fb_width_mm = DisplayWidthMM (dpy, screen); + fb_height_mm = DisplayHeightMM (dpy, screen); + } + } + + apply(); + XSync (dpy, False); + + return True; + +} + + +int +main (int argc, char **argv) +{ + char *display_name = NULL; + int major, minor; + int haderror = 0; + int i; + char msg[256]; + XEvent ev; + int modifier = 0; + int keysym = 0; + + program_name = argv[0]; + + for (i = 1; i < argc; i++) { + if (!strcmp ("-display", argv[i]) || !strcmp ("-d", argv[i])) { + if (++i>=argc) usage (); + display_name = argv[i]; + continue; + } + if (!strcmp ("-key", argv[i]) || !strcmp ("-k", argv[i])) { + if (++i>=argc) usage (); + if ((keysym = XStringToKeysym(argv[i])) == NoSymbol) { + fprintf(stderr, "wrong option: -key %s\n", argv[i]); + usage(); + } + continue; + } + if (!strcmp ("-mod", argv[i]) || !strcmp ("-m", argv[i])) { + int j; + char *s, *p, *q; + int end = 0; + + if (++i>=argc) usage (); + q = s = strdup (argv[i]); + for (; ;) { + if (p = strchr(s, '+')) { + *p = ' '; + while (*(p-1) == ' ') p--; + *p = 0; + } + else + end = 1; + for (j = 0; j < MAX_MODIFIERS; j++) { + if (!strcmp(mod_key_table[j].modname, s)) { + modifier |= mod_key_table[j].mod; + break; + } + } + if (j == MAX_MODIFIERS) { + fprintf(stderr, "wrong option: -mod %s\n", q); + usage(); + } + if (end) + break; + else { + s = ++p; + while (*s == ' ') s++; + } + } + if (q) free (q); + continue; + } + if (!strcmp ("--notwin", argv[i])) { + notwin = True; + continue; + } + if (!strcmp ("--verbose", argv[i])) { + verbose = True; + continue; + } + if (!strcmp ("--testrun", argv[i])) { + testrun = True; + verbose = True; + continue; + } + usage(); + } + + dpy = XOpenDisplay (display_name); + + if (dpy == NULL) + fatal ("can't open display %s\n", XDisplayName(display_name)); + + screen = DefaultScreen (dpy); + root = RootWindow (dpy, screen); + + if (!XRRQueryVersion (dpy, &major, &minor)) + fatal ("randr extension missing\n"); + + if ((major <= 1) && (major != 1 || minor < 2)) + fatal ("wrong randr version\n"); + + haderror = 0; + + /* set default key and modifier if not set in command */ + if (!keysym) + keysym = XStringToKeysym ("F5"); + if (!modifier) + modifier = ShiftMask; + + XGrabKey(dpy, + XKeysymToKeycode(dpy, keysym), + modifier, + root, True, GrabModeAsync, GrabModeAsync); + + if (haderror) { + if (haderror == BadAccess) + fatal ("XGrabKey: Bad Access"); + else { + XGetErrorText (dpy, haderror, msg, sizeof (msg)); + fatal ("XGrabKey: %s", msg); + } + } + + XRRGetScreenSizeRange (dpy, root, &minWidth, &minHeight, + &maxWidth, &maxHeight); + + res = XRRGetScreenResources (dpy, root); + if (!res) + fatal ("could not get screen resources\n"); + if (res->ncrtc != 2) + fatal ("too few or too many crtcs: %d\n", res->ncrtc); + + do_init(); + + for(;;) + { + if (testrun) { + usleep(4000000); + fprintf(stderr, "\n"); + } else + XNextEvent(dpy, &ev); + + if (testrun || (ev.type == KeyPress)) { + struct stat sbuf; + + if (verbose) + fprintf(stderr, "\na key press event was grabbed ...\n"); + + if (pid) { + kill (pid, SIGTERM); + pid = 0; + } + + if (testrun || !stat(DISP_SWITCH_NEEDS_PROBE, &sbuf)) { + /* Too long since last switch, need to check output changes */ + remove (DISP_SWITCH_NEEDS_PROBE); + if (probe_and_check_output_changes ()) { + output_t *output, *next; + + for (output = outputs; output; output = output->next) { + if (output->output_info) { + XRRFreeOutputInfo (output->output_info); + output->output_info = NULL; + } + if (output->crtc_info && output->crtc_info->outputs) { + free(output->crtc_info->outputs); + output->crtc_info->outputs = NULL; + } + } + output = outputs; + while (output) { + next = output->next; + free(output); + output = next; + } + outputs = NULL; + outputs_tail = &outputs; + for (i = 0; i < ncon; i++) { + con_outputs[i].output = NULL; + con_outputs[i].on = False; + if (con_outputs[i].smodes) { + free(con_outputs[i].smodes); + con_outputs[i].smodes = NULL; + } + con_outputs[i].nsmodes = 0; + } + + do_init(); + } + } + + if (!do_not_switch) + if (!do_switch()) { + if ((ncon == 2) && (start == 4)) { + fb_width = DisplayWidth (dpy, screen); + fb_height = DisplayHeight (dpy, screen); + start = 5; + if (verbose) + fprintf(stderr, "too small screen, try skiping twin view\n"); + do_switch(); + } + } + + finish(); + } + } +} + diff --git a/dispswtch.man b/dispswtch.man new file mode 100644 index 0000000..9422e97 --- /dev/null +++ b/dispswtch.man @@ -0,0 +1,80 @@ +'\" t +.\" +.\" Copyright 2001 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 Keith Packard not be used in +.\" advertising or publicity pertaining to distribution of the software without +.\" specific, written prior permission. Keith Packard makes no +.\" representations about the suitability of this software for any purpose. It +.\" is provided "as is" without express or implied warranty. +.\" +.\" KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +.\" EVENT SHALL KEITH PACKARD 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. +.\" +.\" +.\" $XFree86: xc/programs/xrandr/dispswtch.man,v 1.1 2008/07/25 14:12:39 eich Exp $ +.\" +.TH DISPSWTCH __appmansuffix__ __vendorversion__ +.SH NAME +dispswtch \- Display Device Switch +.SH SYNOPSIS +.B "dispswtch" +[\-display \fIdisplay\fP] +[\-key \fIkeysym\fP] +[\-mod \fImodifier\fP] +[\-\-help] +[\-\-verbose] +[\-\-notwin] +[\-\-testrun] +.SH DESCRIPTION +.I Dispswtch +uses hotkey to switch/rotate on/off of display devices up to 3 devices. Twin view is +included when two devices are connected. It works on systems where both server and driver +support RandR version 1.2 (or above). If an ACPI notification interface is connected, and +the notification mechanism is operating, the display device switch function key is the +hotkey. Otherwise user can define a hotkey with -key and -mod options (see below), the default is "Shift + F5". + +If a key stroke is hit 5 seconds or longer after the previous hit, display devices are re-probed so that any changes such as adding or removing devices, can be detected and adapted to. + +If a new display device is added whose mode size is larger than the one with which X server is started, or a twin view state is swtched into that needs a larger mode to cover both screens, a config file specifying larger virtual screen size will be need during X server startup. + +.IP \-\-help +Print out a summary of the usage and exit. +.IP \-\-verbose +Print out debug messages when run. +.IP \-\-notwin +Skip twinview states in two devices rotating. +.IP \-\-testrun +Switch/rotate display device states repeatedly without hotkey strokes. +.IP "\-key " +Define the keysym of hotkey to be , which can be any entry defined in +defined in /usr/X11/include/keysymdef.h, with prefix XK_ removed, such as F5, F8, Escape. +.IP "\-mod " +Define the modifier of hotkey to be . is any or combination of modifiers on your system. Run /usr/X11/bin/xmodmap to find all available modifiers on your system.They can be shift, control, mod1, mod3, mod4. +.SH +Examples: + +-key Escape -mod shift defines hotkey to be 'shift + Esc' +.sp +-key F2 -mod "shift+control" defines hotkey to be 'shift + control + F2' + +See \fBattributes\fR(5) for descriptions of the following attributes: +.sp +.TS +allbox; +cw(2.750000i)| cw(2.750000i) +lw(2.750000i)| lw(2.750000i). +ATTRIBUTE TYPE ATTRIBUTE VALUE +Availability SUNWxorg-client-programs +Interface Stability Volatile +.TE +.sp +.\" End Sun update