/* * Copyright © 2001 Keith Packard, member of The XFree86 Project, Inc. * Copyright © 2002 Hewlett Packard Company, Inc. * Copyright © 2006 Intel Corporation * * 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. * */ /* * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static char *program_name; static Display *dpy = NULL; static Window root, win; static int screen; static Bool nosideview = False; static Bool verbose = False; static Bool testrun = False; static int had_error = 0; static XErrorHandler prev_handler; static int cur_keycode; static struct timeval time_val; static Rotation init_rotation; static int init_x; static int init_y; static Bool use_init_pos = False; static Bool need_off_deferred = False; static void usage(void) { fprintf(stderr, "usage: %s [options]\n", program_name); fprintf(stderr, " where options are:\n"); fprintf(stderr, " -toggle or -t\n"); fprintf(stderr, " -listen or -l\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, " -nosideview\n"); fprintf(stderr, " -verbose or -v\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*/ } static int cur_handler ( Display *const err_display, XErrorEvent *const err_event) { had_error = err_event -> error_code; return (0); } 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; 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.0; static Bool dryrun = False; static int minWidth, maxWidth, minHeight, maxHeight; #define MAX_OUTPUT 3 #define MAX_MODIFIERS 10 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; unsigned 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 = 0; static int ncon; static int dis_ncon; static Bool do_not_switch = False; static Bool did_init = False; static mod_key_table_t mod_key_table [MAX_MODIFIERS] = { {"none", 0}, {"shift", ShiftMask}, {"lock", LockMask}, {"control", ControlMask}, {"mod1", Mod1Mask}, {"mod2", Mod2Mask}, {"mod3", Mod3Mask}, {"mod4", Mod4Mask}, {"mod5", Mod5Mask}, {"any", AnyModifier} }; 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; } 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 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; 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) { crtc_t *crtc; /* 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; crtc = find_crtc_by_xid (output->output_info->crtc); /* set position */ if (crtc && crtc->crtc_info) { output->x = crtc->crtc_info->x; output->y = crtc->crtc_info->y; } /* set rotation */ output->rotation &= ~0xf; if (crtc && crtc->crtc_info) output->rotation |= (crtc->crtc_info->rotation & 0xf); else output->rotation = RR_Rotate_0; } 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_for_outputs (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 (0x%lx) : 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 (0x%lx) : %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) { /* comment it out because DisplayWidth() does not reflect the change of fb_width and fb_height previously set by XRRSetScreenSize() */ /* 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); } static 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; } if (need_off_deferred) /* Defer taking off */ 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 */ static 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' */ static 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; } static 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; 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; fb_width = fb_height = 0; 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) { if (verbose) 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; } static void disable_outputs (output_t *outputs) { while (outputs) { outputs->crtc_info = NULL; outputs = outputs->next; } } /* * find the best mapping from output to crtc available */ static 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 */ static 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 0x%lx 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 (!new_res) fatal ("could not get screen resources"); if ((new_res->noutput != res->noutput) || (new_res->nmode != res->nmode) || (new_res->ncrtc != res->ncrtc)) 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; } for (i = 0; i < new_res->ncrtc; i++) { crtc_t *crtc = NULL; /* old */ XRRCrtcInfo *crtc_info = NULL; /* new */ crtc = find_crtc_by_xid (res->crtcs[i]); crtc_info = XRRGetCrtcInfo (dpy, new_res, new_res->crtcs[i]); if (!crtc || !crtc_info) { changed = True; break; } if (!crtc->mode_info && !find_mode_by_xid (crtc_info->mode)) continue; if ((crtc_info->x != crtc->x) || (crtc_info->y != crtc->y) || (find_mode_by_xid (crtc_info->mode) != crtc->mode_info) || (crtc_info->rotation != crtc->rotation)) { 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 Bool need_probe (void) { struct timeval cur_time_val; long long cur, prev; X_GETTIMEOFDAY(&cur_time_val); cur = (long long) cur_time_val.tv_sec * 1000000 + cur_time_val.tv_usec; prev =(long long) time_val.tv_sec * 1000000 + time_val.tv_usec; if (((cur - prev) < 0) || ((cur - prev) > 5000000)) return True; else return False; } 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 get_common_mode(con_output_t *c0, con_output_t *c1, int *m0, int *m1) { int i, j; int i1 = -1, j1 = -1, i2 = -1, j2 = -1; int x, y, w, h; output_t *output = c0->output; *m0 = -1; *m0 = -1; if (!c0 ||!c1 || !c0->smodes || !c1->smodes) return False; /* first try to find mode with common same size */ 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)) { x = output->x; y = output->y; w = mode_width (c0->smodes[i], output->rotation); h = mode_height (c0->smodes[i], output->rotation); if ((x + w <= maxWidth) && (y + h <= maxHeight)) { i1 = i; j1 = j; break; } } if ((i1 != -1) && (j1 != -1)) break; } if ((i1 == -1) && (j1 == -1)) return False; /* then try to find mode with common id for possible cloning */ for (i = 0; i < c0->nsmodes; i ++) { for (j = 0; j < c1->nsmodes; j ++) if (c0->smodes[i] == c1->smodes[j]) { x = output->x; y = output->y; w = mode_width (c0->smodes[i], output->rotation); h = mode_height (c0->smodes[i], output->rotation); if ((x + w <= maxWidth) && (y + h <= maxHeight)) { i2 = i; j2 = j; break; } } if ((i2 != -1) && (j2 != -1)) break; } if ((i2 == -1) && (j2 == -1)) { *m0 = i1; *m1 = j1; } else { /* use common id if it is not smaller */ if ((mode_width (c0->smodes[i1], output->rotation) > mode_width (c0->smodes[i2], output->rotation)) && (mode_height (c0->smodes[i1], output->rotation) > mode_height (c0->smodes[i2], output->rotation))) { *m0 = i1; *m1 = j1; } else { *m0 = i2; *m1 = j2; } } return True; } static XRRModeInfo * get_largest_mode (con_output_t *c, XRRModeInfo *start_mode) { int i, found = False; output_t *output = c->output; for (i = 0; i < c->nsmodes; i++) { XRRModeInfo *mode_info = c->smodes[i]; int x, y, w, h; if (!found && (start_mode != mode_info)) continue; else found = True; if (mode_info) { x = output->x; y = output->y; w = mode_width (mode_info, output->rotation); h = mode_height (mode_info, output->rotation); if ((x + w <= maxWidth) && (y + h <= maxHeight)) break; } } if (i < c->nsmodes) return c->smodes[i]; else fatal("cannot find mode"); } static Bool valid_mode(con_output_t *con, XRRModeInfo *mode) { int i; for (i = 0; i < con->nsmodes; i++) if (con->smodes[i] == mode) return True; return False; } static 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; init_rotation = RR_Rotate_0; init_x = 0; init_y = 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) { crtc_t *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"); } } /* set initial mode_info */ crtc = find_crtc_by_xid (output_info->crtc); if (crtc) con_outputs[ncon].output->mode_info = find_mode_by_xid (crtc->crtc_info->mode); } 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, rotation = %d, crtcs =", j, con_outputs[j].output->output_info->name, con_outputs[j].smodes[0]->name, con_outputs[j].output->rotation); for (i = 0; i < con_outputs[j].output->output_info->ncrtc; i++) fprintf(stderr, " 0x%lx", con_outputs[j].output->output_info->crtcs[i]); fprintf(stderr, ", using 0x%lx", 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, " 0x%lx", dis_con_outputs[j].output->output_info->crtcs[i]); fprintf(stderr, ", using 0x%lx", 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; if ((i == 1) || (i == 2) || (i == 4)) { use_init_pos = True; j = i >> 1; /* remember position and mode info in single state */ start_mode[j] = con_outputs[j].output->mode_info; init_rotation = con_outputs[j].output->rotation; init_x = con_outputs[j].output->x; init_y = con_outputs[j].output->y; } else use_init_pos = False; if ((ncon != 2) || (start < 3)) start = i; if ((ncon < 1) || (ncon > 3)) { if ((ncon < 1) && verbose) fprintf (stderr, "warn: no connection\n"); else if ((ncon > 3) && verbose) fprintf (stderr, "warn: too many (more than 3) connections: %d: can't switch\n", ncon); do_not_switch = True; } did_init = True; return; } static int grab_key (Display *dpy, int keysym, unsigned int modifier, Window grab_window) { char msg[256]; int keycode = XKeysymToKeycode(dpy, keysym); if (keycode == NoSymbol) fatal ("grab_key: keycode not defined for keysym 0x%x\n", keysym); had_error = 0; prev_handler = XSetErrorHandler (cur_handler); if (!testrun) { XGrabKey(dpy, keycode, modifier, root, True, GrabModeAsync, GrabModeAsync); XSync (dpy, False); } XSetErrorHandler (prev_handler); if (had_error) { XGetErrorText (dpy, had_error, msg, sizeof (msg)); fatal ("XGrabKey: %s\n", msg); } if (verbose) fprintf(stderr, "keycode to grab: %d\n", keycode); return keycode; } static Bool do_switch (void) { int i, j; int single; int save = -1; if (ncon <= 0) return True; for (i = 0; i < ncon; i++) { output_t *output = con_outputs[i].output; new_mode[i] = NULL; output->relation = same_as; output->relative_to = NULL; if (use_init_pos) { output->x = init_x; output->y = init_y; output->rotation = init_rotation; } else { output->x = 0; output->y = 0; } } if (ncon == 2) { if (!nosideview) { 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 (get_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] = get_largest_mode (&con_outputs[0], con_outputs[0].smodes[0]); new_mode[1] = get_largest_mode (&con_outputs[1], con_outputs[1].smodes[0]); } } else { if (start_mode[start -1] && valid_mode(&con_outputs[start -1], start_mode[start -1])) new_mode[start -1] = start_mode[start -1]; else { if (con_outputs[start -1].smodes[0]) new_mode[start -1] = get_largest_mode (&con_outputs[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] && valid_mode(&con_outputs[i], start_mode[i])) new_mode[i] = start_mode[i]; else { if (con_outputs[i].smodes[0]) new_mode[i] = get_largest_mode (&con_outputs[i], con_outputs[i].smodes[0]); } } else { int m0, m1; if (get_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] = get_largest_mode (&con_outputs[i], con_outputs[i].smodes[0]); new_mode[j] = get_largest_mode (&con_outputs[j], con_outputs[j].smodes[0]); } } } if (ncon == 1) { if (start_mode[0] && valid_mode(&con_outputs[0], start_mode[0])) new_mode[0] = start_mode[0]; else { if (con_outputs[0].smodes[0]) new_mode[0] = get_largest_mode (&con_outputs[0], con_outputs[0].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 rotation %d\n", i, con_outputs[i].output->output_info->name, con_outputs[i].output->mode_info->name, con_outputs[i].output->rotation); } } else if (con_outputs[i].on ) { if (!need_off_deferred) { 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); } else save = i; } } if ((ncon == 2) && (start >= 4)) { if (start == 4) { con_outputs[1].output->relative_to = con_outputs[0].output; con_outputs[1].output->relation = right_of; con_outputs[1].output->changes = changes_relation; con_outputs[0].output->changes = 0; } else if (start == 5) { con_outputs[0].output->relative_to = con_outputs[1].output; con_outputs[0].output->relation = right_of; con_outputs[0].output->changes = changes_relation; con_outputs[1].output->changes = 0; } set_positions(); } if (!set_screen_size ()) return False; /* reset crtcs before allocation */ reset_crtcs_for_outputs(); if (!did_init) get_crtcs(); if (!pick_crtcs()) { if (verbose) fprintf(stderr, "pick_crtcs failed\n"); return True; } set_crtcs (); apply(); if (need_off_deferred && (save != -1)) { /* Now, take the deferred output off */ output_t *output; crtc_t *crtc; Status s; output = con_outputs[save].output; output->mode_info = NULL; con_outputs[save].on = False; if (verbose) fprintf(stderr, "turn off output %d (%s) \n", save, con_outputs[save].output->output_info->name); crtc = output->crtc_info; s = crtc_disable (crtc); if (s != RRSetConfigSuccess) panic (s, crtc); } XSync (dpy, False); did_init = False; return True; } static Bool do_toggle (void) { Atom atom; XEvent xev; int ret; atom = XInternAtom (dpy, "DISPLAYSWITCH_DAEMON", True); if (!atom) { fprintf(stderr, "dispswitch daemon not running\n"); return False; } win = XGetSelectionOwner (dpy, atom); if (!win) { fprintf(stderr, "dispswitch: No owner of dispswitch daemon is found\n"); return False; } bzero (&xev, sizeof (XEvent)); xev.xkey.type = KeyPress; xev.xkey.send_event = True; xev.xkey.display = dpy; /* Any keycode */ xev.xkey.keycode = 71; /* * Send another instance of dispswitch (a daemon) an event to * request a switch */ ret = XSendEvent(dpy, win, False, KeyPressMask, &xev); XFlush(dpy); if (!ret) fprintf(stderr, "dispswitch: XSendEvent error\n"); return (!ret); } int main (int argc, char **argv) { char *display_name = NULL; int major, minor; int i; char msg[256]; XEvent ev; unsigned int modifier = 0; Bool key_given = False; Bool mod_given = False; int keysym = 0, toggle = False, listen = False; Atom atom; 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, "invalid keysym: -key %s\n", argv[i]); usage(); } key_given = True; continue; } if (!strcmp ("-mod", argv[i]) || !strcmp ("-m", argv[i])) { int j; char *s, *p, *q; int end = 0; if (++i>=argc) usage (); s = strdup (argv[i]); if (!s) { if (verbose) fprintf(stderr, "modifier failed, will use default modifier\n"); continue; } while (*s == ' ') s++; p = s + strlen(s) - 1; while (*p == ' ') *p-- = 0; q = s; for (; ;) { if (p = strchr(s, '+')) { *p = ' '; while ((p > s) && (*(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, "invalid modifier: -mod %s\n", q); usage(); } if (end) break; else { s = ++p; while (*s == ' ') s++; } } mod_given = True; free (q); continue; } if (!strcmp ("-nosideview", argv[i])) { nosideview = True; continue; } if (!strcmp ("-verbose", argv[i]) || !strcmp ("-v", argv[i])) { verbose = True; continue; } if (!strcmp ("-testrun", argv[i])) { testrun = True; verbose = True; continue; } if (!strcmp ("-toggle", argv[i]) || !strcmp ("-t", argv[i])) { toggle = True; continue; } if (!strcmp ("-listen", argv[i]) || !strcmp ("-l", argv[i])) { listen = 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: %d.%d\n", major, minor); if (toggle) exit (do_toggle()); /* * Create an atom, a trivial window, and make it selection owner. * Ready to accept a client event request for switch */ atom = XInternAtom(dpy, "DISPLAYSWITCH_DAEMON", False); if (!atom) { if (verbose) fprintf(stderr, "cannot create Atom\n"); } else { if (XGetSelectionOwner (dpy, atom)) { if (verbose) fprintf(stderr, "dispswitch daemon is already running, quit\n"); exit (1); } win = XCreateSimpleWindow(dpy, root, 0, 0, 10, 10, 0, 10, 0); if (!win) { if (verbose) fprintf(stderr, "cannot create window\n"); } else { XSetSelectionOwner(dpy, atom, win, CurrentTime); if (XGetSelectionOwner(dpy, atom) != win) { if (verbose) fprintf(stderr, "set selection owner failed\n"); } else XSelectInput(dpy, win, KeyPressMask); } } /* set default key and modifier if not given in command */ if (!key_given) keysym = XStringToKeysym ("F5"); if (!mod_given) modifier = ShiftMask; if (!listen) cur_keycode = grab_key (dpy, keysym, modifier, root); XRRGetScreenSizeRange (dpy, root, &minWidth, &minHeight, &maxWidth, &maxHeight); fb_width_mm = DisplayWidthMM (dpy, screen); fb_height_mm = DisplayHeightMM (dpy, screen); dpi = (25.4 * DisplayHeight (dpy, screen)) / DisplayHeightMM(dpy, screen); res = XRRGetScreenResources (dpy, root); if (!res) fatal ("could not get screen resources\n"); if (res->ncrtc < 2) fatal ("too few crtcs: %d\n", res->ncrtc); do_init(); X_GETTIMEOFDAY(&time_val); for(;;) { need_off_deferred = False; if (testrun) { usleep(4000000); fprintf(stderr, "\n"); } else XNextEvent(dpy, &ev); if (!listen && !testrun && (ev.type == MappingNotify) && ((ev.xmapping.request == MappingKeyboard) || (ev.xmapping.request == MappingModifier))) { /* keyboard/modifier mapping changed */ if (verbose) fprintf(stderr, "\nkeyboard/modifier mapping changed ...\n"); XUngrabKey(dpy, cur_keycode, modifier, root); cur_keycode = grab_key (dpy, keysym, modifier, root); } if (testrun || (ev.type == KeyPress)) { if (verbose) fprintf(stderr, "\na key press event was grabbed ...\n"); do_not_switch = False; if (testrun || need_probe()) { /* Too long since last switch, need to check output changes */ if (probe_and_check_output_changes ()) { output_t *output, *next; output = outputs; while (output) { if (output->output_info) XRRFreeOutputInfo (output->output_info); if (output->crtc_info && output->crtc_info->outputs) { free(output->crtc_info->outputs); output->crtc_info->outputs = NULL; } 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(); } else if (ncon == 1) do_not_switch = True; } else if (ncon == 1) do_not_switch = True; if (!do_not_switch) { if ((ncon == 2) && (start == 1)) /* * Workaround for intel based graphics: in switching from * LVDS to VGA, off on LVDS needs to be deferred. */ need_off_deferred = True; if (!do_switch()) { if ((ncon == 2) && (start == 4)) { start = 5; if (verbose) fprintf(stderr, "too small screen, skipping side view\n"); (void) do_switch(); } } } X_GETTIMEOFDAY(&time_val); } } }