#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rxrandr.h" static char *program_name; static Display *dpy; static Window root; static int screen = -1; static Bool verbose = False; static Bool automatic = False; static Bool grab_server = True; static Bool no_primary = False; static int filter_type = -1; static const char *filter_names[2] = { "bilinear", "nearest" }; static const char *direction[5] = { "normal", "left", "inverted", "right", "\n" }; static void _X_NORETURN _X_ATTRIBUTE_PRINTF(1, 2) fatal(const char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: ", program_name); vfprintf(stderr, format, ap); va_end(ap); exit(1); } static void _X_ATTRIBUTE_PRINTF(1, 2) warning(const char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: ", program_name); vfprintf(stderr, format, ap); va_end(ap); } static void _X_NORETURN _X_ATTRIBUTE_PRINTF(1, 2) argerr(const char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "%s: ", program_name); vfprintf(stderr, format, ap); fprintf(stderr, "Try '%s --help' for more information.\n", program_name); va_end(ap); exit(1); } static inline double dmin(double x, double y) { return x < y ? x : y; } static const char* rotation_name(Rotation rotation) { int i; if ((rotation & 0xf) == 0) return "normal"; for (i = 0; i < 4; i++) if (rotation & (1 << i)) return direction[i]; return "invalid rotation"; } static const char* reflection_name(Rotation rotation) { rotation &= (RR_Reflect_X | RR_Reflect_Y); switch (rotation) { case 0: return "none"; case RR_Reflect_X: return "X axis"; case RR_Reflect_Y: return "Y axis"; case RR_Reflect_X | RR_Reflect_Y: return "X and Y axis"; } return "invalid reflection"; } typedef enum _relation { relation_left_of, relation_right_of, relation_above, relation_below, relation_same_as, } relation_t; typedef struct { int x, y, width, height; } rectangle_t; typedef struct { int x1, y1, x2, y2; } box_t; typedef struct { int x, y; } point_t; typedef enum _changes { changes_none = 0, changes_crtc = (1 << 0), changes_mode = (1 << 1), changes_relation = (1 << 2), changes_position = (1 << 3), changes_rotation = (1 << 4), changes_reflection = (1 << 5), changes_automatic = (1 << 6), changes_refresh = (1 << 7), changes_property = (1 << 8), changes_transform = (1 << 9), changes_panning = (1 << 10), changes_gamma = (1 << 11), changes_primary = (1 << 12), changes_filter = (1 << 13), } changes_t; typedef enum _name_kind { name_none = 0, name_string = (1 << 0), name_xid = (1 << 1), name_index = (1 << 2), name_preferred = (1 << 3), } 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; typedef struct _transform transform_t; typedef struct _umode umode_t; typedef struct _output_prop output_prop_t; typedef struct _provider provider_t; typedef struct _monitors monitors_t; typedef struct _umonitor umonitor_t; struct _transform { XTransform transform; const char *filter; int nparams; XFixed *params; }; struct _crtc { name_t crtc; Bool changing; XRRCrtcInfo *crtc_info; XRRModeInfo *mode_info; XRRPanning *panning_info; int x; int y; Rotation rotation; output_t **outputs; int noutput; transform_t current_transform, pending_transform; }; struct _output_prop { struct _output_prop *next; char *name; char *value; }; struct _output { struct _output *next; changes_t changes; output_prop_t *props; name_t output; XRROutputInfo *output_info; name_t crtc; crtc_t *crtc_info; crtc_t *current_crtc_info; name_t mode; double refresh; XRRModeInfo *mode_info; name_t addmode; relation_t relation; char *relative_to; int x, y; Rotation rotation; XRRPanning panning; Bool automatic; int scale_from_w, scale_from_h; transform_t transform; struct { float red; float green; float blue; } gamma; float brightness; Bool primary; Bool found; }; typedef enum _umode_action { umode_create, umode_destroy, umode_add, umode_delete } umode_action_t; struct _umode { struct _umode *next; umode_action_t action; XRRModeInfo mode; name_t output; name_t name; }; struct _provider { name_t provider; XRRProviderInfo *info; }; struct _monitors { int n; XRRMonitorInfo *monitors; }; struct _umonitor { struct _umonitor *next; char *name; Bool set; Bool primary; int x, y, width, height; int mmwidth, mmheight; int noutput; name_t *outputs; }; #define OUTPUT_NAME 1 #define CRTC_OFF 2 #define CRTC_UNSET 3 #define CRTC_INDEX 0x40000000 #define MODE_NAME 1 #define MODE_OFF 2 #define MODE_UNSET 3 #define MODE_PREF 4 #define POS_UNSET -1 static output_t *all_outputs = NULL; static output_t **all_outputs_tail = &all_outputs; static crtc_t *crtcs; static int num_crtcs; static XRRScreenResources *res; static int fb_width = 0, fb_height = 0; static int fb_width_mm = 0, fb_height_mm = 0; static double dpi = 0; static Bool dryrun = False; static int minWidth, maxWidth, minHeight, maxHeight; static Bool has_1_2 = False; static Bool has_1_3 = False; static Bool has_1_4 = False; static Bool has_1_5 = False; static monitors_t *monitors; 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; } } static Bool transform_point(XTransform *transform, double *xp, double *yp) { double vector[3]; double result[3]; int i, j; double v; vector[0] = *xp; vector[1] = *yp; vector[2] = 1; for (j = 0; j < 3; j++) { v = 0; for (i = 0; i < 3; i++) v += (XFixedToDouble(transform->matrix[j][i]) * vector[i]); result[j] = v; } if (!result[2]) return False; for (j = 0; j < 2; j++) { vector[j] = result[j] / result[2]; if (vector[j] > 32767 || vector[j] < -32767) return False; } *xp = vector[0]; *yp = vector[1]; return True; } static void path_bounds(XTransform *transform, point_t *points, int npoints, box_t *box) { int i; box_t point; for (i = 0; i < npoints; i++) { double x, y; x = points[i].x; y = points[i].y; transform_point(transform, &x, &y); point.x1 = floor(x); point.y1 = floor(y); point.x2 = ceil(x); point.y2 = ceil(y); if (i == 0) *box = point; else { if (point.x1 < box->x1) box->x1 = point.x1; if (point.y1 < box->y1) box->y1 = point.y1; if (point.x2 > box->x2) box->x2 = point.x2; if (point.y2 > box->y2) box->y2 = point.y2; } } } static void mode_geometry(XRRModeInfo *mode_info, Rotation rotation, XTransform *transform, box_t *bounds) { point_t rect[4]; int width = mode_width(mode_info, rotation); int height = mode_height(mode_info, rotation); rect[0].x = 0; rect[0].y = 0; rect[1].x = width; rect[1].y = 0; rect[2].x = width; rect[2].y = height; rect[3].x = 0; rect[3].y = height; path_bounds(transform, rect, 4, bounds); } static double mode_refresh(const XRRModeInfo *mode_info) { double rate; double vTotal = mode_info->vTotal; if (mode_info->modeFlags & RR_DoubleScan) { vTotal *= 2; } if (mode_info->modeFlags & RR_Interlace) { vTotal /= 2; } if (mode_info->hTotal && vTotal) rate = ((double) mode_info->dotClock / ((double) mode_info->hTotal * (double) vTotal)); else rate = 0; return rate; } static void init_name(name_t *name) { memset(name, 0, sizeof(*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 idx) { name->kind |= name_index; name->index = idx; } static void set_name_preferred(name_t *name) { name->kind |= name_preferred; } 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) { unsigned int xid; int idx; if ((valid & name_xid) && sscanf(string, "0x%x", &xid) == 1) set_name_xid(name, xid); else if ((valid & name_index) && sscanf(string, "%d", &idx) == 1) set_name_index(name, idx); else if (valid & name_string) set_name_string(name, string); else argerr("invalid name '%s'\n", string); } static void init_transform(transform_t *transform) { int x; memset(&transform->transform, '\0', sizeof(transform->transform)); for (x = 0; x < 3; x++) transform->transform.matrix[x][x] = XDoubleToFixed(1.0); transform->filter = ""; transform->nparams = 0; transform->params = NULL; } static void set_transform(transform_t *dest, XTransform *transform, const char *filter, XFixed *params, int nparams) { dest->transform = *transform; dest->filter = strdup(filter); dest->nparams = nparams; dest->params = malloc(nparams * sizeof(XFixed)); memcpy(dest->params, params, nparams * sizeof(XFixed)); } static void copy_transform(transform_t *dest, transform_t *src) { set_transform(dest, &src->transform, src->filter, src->params, src->nparams); } static Bool equal_transform(transform_t *a, transform_t *b) { if (memcmp(&a->transform, &b->transform, sizeof(XTransform)) != 0) return False; if (strcmp(a->filter, b->filter) != 0) return False; if (a->nparams != b->nparams) return False; if (memcmp(a->params, b->params, a->nparams * sizeof(XFixed)) != 0) return False; return True; } static output_t* add_output(void) { output_t *output = calloc(1, sizeof(output_t)); if (!output) fatal("out of memory\n"); output->next = NULL; output->found = False; output->brightness = 1.0; *all_outputs_tail = output; all_outputs_tail = &output->next; return output; } static output_t* find_output(name_t *name) { output_t *output; for (output = all_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 output_t* find_output_by_name(char *name) { name_t output_name; init_name(&output_name); set_name_string(&output_name, name); 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, double refresh) { int m; XRRModeInfo *best = NULL; double 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; } if ((name->kind & name_string) && !strcmp(name->string, mode->name)) { double dist; if (refresh) dist = fabs(mode_refresh(mode) - refresh); else dist = 0; if (!best || dist < bestDist) { bestDist = dist; best = mode; } } } 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, 0); } static XRRModeInfo* find_mode_for_output(output_t *output, name_t *name) { XRROutputInfo *output_info = output->output_info; int m; XRRModeInfo *best = NULL; double bestDist = 0; for (m = 0; m < output_info->nmode; m++) { XRRModeInfo *mode; mode = find_mode_by_xid(output_info->modes[m]); if (!mode) continue; if ((name->kind & name_xid) && name->xid == mode->id) { best = mode; break; } if ((name->kind & name_string) && !strcmp(name->string, mode->name)) { double dist; if (!output->refresh && (mode->modeFlags & RR_DoubleScan)) continue; if (output->refresh) dist = fabs(mode_refresh(mode) - output->refresh); else dist = 0; if (!best || dist < bestDist) { bestDist = dist; best = mode; } } } return best; } static XRRModeInfo* preferred_mode(output_t *output) { XRROutputInfo *output_info = output->output_info; int m; XRRModeInfo *best; int bestDist; best = NULL; bestDist = 0; for (m = 0; m < output_info->nmode; m++) { XRRModeInfo *mode_info = find_mode_by_xid(output_info->modes[m]); int dist; if (m < output_info->npreferred) dist = 0; else if (output_info->mm_height) dist = (1000 * DisplayHeight(dpy, screen) / DisplayHeightMM(dpy, screen) - 1000 * mode_info->height / output_info->mm_height); else dist = DisplayHeight(dpy, screen) - mode_info->height; if (dist < 0) dist = -dist; if (!best || dist < bestDist) { best = mode_info; bestDist = dist; } } return best; } static Bool output_can_use_crtc(output_t *output, crtc_t *crtc) { XRROutputInfo *output_info = output->output_info; int c; for (c = 0; c < output_info->ncrtc; c++) if (output_info->crtcs[c] == crtc->crtc.xid) return True; return False; } static Bool output_can_use_mode(output_t *output, XRRModeInfo *mode) { XRROutputInfo *output_info = output->output_info; int m; for (m = 0; m < output_info->nmode; m++) if (output_info->modes[m] == mode->id) return True; return False; } static Bool crtc_can_use_rotation(crtc_t *crtc, Rotation rotation) { Rotation rotations = crtc->crtc_info->rotations; Rotation dir = rotation & (RR_Rotate_0 | RR_Rotate_90 | RR_Rotate_180 | RR_Rotate_270); Rotation reflect = rotation & (RR_Reflect_X | RR_Reflect_Y); if (((rotations & dir) != 0) && ((rotations & reflect) == reflect)) return True; return False; } static Bool output_can_use_rotation(output_t *output, Rotation rotation) { XRROutputInfo *output_info = output->output_info; int c; for (c = 0; c < output_info->ncrtc; c++) { crtc_t *crtc = find_crtc_by_xid(output_info->crtcs[c]); if (crtc && !crtc_can_use_rotation(crtc, rotation)) return False; } return True; } static Bool output_is_primary(output_t *output) { if (has_1_3) return XRRGetOutputPrimary(dpy, root) == output->output.xid; return False; } static int find_last_non_clamped(CARD16 array[], int size) { int i; for (i = size - 1; i > 0; i--) { if (array[i] < 0xffff) return i; } return 0; } static void set_gamma_info(output_t *output) { XRRCrtcGamma *crtc_gamma; double i1, v1, i2, v2; int size, middle, last_best, last_red, last_green, last_blue; CARD16 *best_array; if (!output->crtc_info) return; size = XRRGetCrtcGammaSize(dpy, output->crtc_info->crtc.xid); if (!size) { warning("Failed to get size of gamma for output %s\n", output->output.string); return; } crtc_gamma = XRRGetCrtcGamma(dpy, output->crtc_info->crtc.xid); if (!crtc_gamma) { warning("Failed to get gamma for output %s\n", output->output.string); return; } last_red = find_last_non_clamped(crtc_gamma->red, size); last_green = find_last_non_clamped(crtc_gamma->green, size); last_blue = find_last_non_clamped(crtc_gamma->blue, size); best_array = crtc_gamma->red; last_best = last_red; if (last_green > last_best) { last_best = last_green; best_array = crtc_gamma->green; } if (last_blue > last_best) { last_best = last_blue; best_array = crtc_gamma->blue; } if (last_best == 0) last_best = 1; middle = last_best / 2; i1 = (double) (middle + 1) / size; v1 = (double) (best_array[middle]) / 65535; i2 = (double) (last_best + 1) / size; v2 = (double) (best_array[last_best]) / 65535; if (v2 < 0.0001) { output->brightness = 0; output->gamma.red = 1; output->gamma.green = 1; output->gamma.blue = 1; } else { if ((last_best + 1) == size) output->brightness = v2; else output->brightness = exp((log(v2) * log(i1) - log(v1) * log(i2)) / log(i1 / i2)); output->gamma.red = log((double) (crtc_gamma->red[last_red / 2]) / output->brightness / 65535) / log((double) ((last_red / 2) + 1) / size); output->gamma.green = log((double) (crtc_gamma->green[last_green / 2]) / output->brightness / 65535) / log((double) ((last_green / 2) + 1) / size); output->gamma.blue = log((double) (crtc_gamma->blue[last_blue / 2]) / output->brightness / 65535) / log((double) ((last_blue / 2) + 1) / size); } XRRFreeGamma(crtc_gamma); } static void set_output_info(output_t *output, RROutput xid, XRROutputInfo *output_info) { if (output_info->connection != RR_Disconnected && !output_info->nmode) warning("Output %s is not disconnected but has no modes\n", output_info->name); 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; if (!(output->changes & changes_crtc)) set_name_xid(&output->crtc, output_info->crtc); if (output->crtc.kind == name_xid && output->crtc.xid == None) output->crtc_info = NULL; else { output->crtc_info = find_crtc(&output->crtc); if (!output->crtc_info) { if (output->crtc.kind & name_xid) fatal("cannot find crtc 0x%lx\n", output->crtc.xid); if (output->crtc.kind & name_index) fatal("cannot find crtc %d\n", output->crtc.index); } if (!output_can_use_crtc(output, output->crtc_info)) fatal("output %s cannot use crtc 0x%lx\n", output->output.string, output->crtc_info->crtc.xid); } if (!(output->changes & changes_mode)) { crtc_t *crtc = NULL; if (output_info->crtc) crtc = find_crtc_by_xid(output_info->crtc); if (crtc && crtc->crtc_info) set_name_xid(&output->mode, crtc->crtc_info->mode); else if (output->crtc_info) set_name_xid(&output->mode, output->crtc_info->crtc_info->mode); else set_name_xid(&output->mode, None); if (output->mode.xid) { output->mode_info = find_mode_by_xid(output->mode.xid); if (!output->mode_info) fatal("server did not report mode 0x%lx for output %s\n", output->mode.xid, output->output.string); } else output->mode_info = NULL; } else if (output->mode.kind == name_xid && output->mode.xid == None) output->mode_info = NULL; else { if (output->mode.kind == name_preferred) output->mode_info = preferred_mode(output); else output->mode_info = find_mode_for_output(output, &output->mode); if (!output->mode_info) { if (output->mode.kind & name_preferred) fatal("cannot find preferred mode\n"); if (output->mode.kind & name_string) fatal("cannot find mode %s\n", output->mode.string); if (output->mode.kind & name_xid) fatal("cannot find mode 0x%lx\n", output->mode.xid); } if (!output_can_use_mode(output, output->mode_info)) fatal("output %s cannot use mode %s\n", output->output.string, output->mode_info->name); } if (!(output->changes & changes_position)) { if (output->crtc_info) { output->x = output->crtc_info->crtc_info->x; output->y = output->crtc_info->crtc_info->y; } else { output->x = 0; output->y = 0; } } if (!(output->changes & changes_rotation)) { output->rotation &= ~0xf; if (output->crtc_info) output->rotation |= (output->crtc_info->crtc_info->rotation & 0xf); else output->rotation = RR_Rotate_0; } if (!(output->changes & changes_reflection)) { 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)); } if (!output_can_use_rotation(output, output->rotation)) fatal("output %s cannot use rotation \"%s\" reflection \"%s\"\n", output->output.string, rotation_name(output->rotation), reflection_name(output->rotation)); if (!(output->changes & changes_gamma)) set_gamma_info(output); if (!(output->changes & changes_transform)) { if (output->crtc_info) copy_transform(&output->transform, &output->crtc_info->current_transform); else init_transform(&output->transform); } else { if (output->scale_from_w > 0 && output->mode_info) { double sx = (double) output->scale_from_w / output->mode_info->width; double sy = (double) output->scale_from_h / output->mode_info->height; if (verbose) printf("scaling %s by %lfx%lf\n", output->output.string, sx, sy); init_transform(&output->transform); output->transform.transform.matrix[0][0] = XDoubleToFixed(sx); output->transform.transform.matrix[1][1] = XDoubleToFixed(sy); output->transform.transform.matrix[2][2] = XDoubleToFixed(1.0); if (sx != 1 || sy != 1) output->transform.filter = "bilinear"; else output->transform.filter = "nearest"; output->transform.nparams = 0; output->transform.params = NULL; } } if (output->changes & changes_filter) { output->transform.filter = filter_names[filter_type]; } if (!(output->changes & changes_primary)) output->primary = output_is_primary(output); } static void get_screen(Bool current) { if (!has_1_2) fatal("Server RandR version before 1.2\n"); if (res) return; XRRGetScreenSizeRange(dpy, root, &minWidth, &minHeight, &maxWidth, &maxHeight); if (current) res = XRRGetScreenResourcesCurrent(dpy, root); else res = XRRGetScreenResources(dpy, root); if (!res) fatal("could not get screen resources"); } static void get_crtcs(void) { int c; num_crtcs = res->ncrtc; crtcs = calloc(num_crtcs, sizeof(crtc_t)); if (!crtcs) fatal("out of memory\n"); for (c = 0; c < res->ncrtc; c++) { XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(dpy, res, res->crtcs[c]); XRRCrtcTransformAttributes *attr; XRRPanning *panning_info = NULL; if (has_1_3) { XRRPanning zero; memset(&zero, 0, sizeof(zero)); panning_info = XRRGetPanning(dpy, res, res->crtcs[c]); zero.timestamp = panning_info->timestamp; if (!memcmp(panning_info, &zero, sizeof(zero))) { Xfree(panning_info); panning_info = NULL; } } 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%lx information\n", res->crtcs[c]); crtcs[c].crtc_info = crtc_info; crtcs[c].panning_info = panning_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; } if (XRRGetCrtcTransform(dpy, res->crtcs[c], &attr) && attr) { set_transform(&crtcs[c].current_transform, &attr->currentTransform, attr->currentFilter, attr->currentParams, attr->currentNparams); XFree(attr); } else { init_transform(&crtcs[c].current_transform); } copy_transform(&crtcs[c].pending_transform, &crtcs[c].current_transform); } } 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 = malloc(sizeof(output_t*)); crtc->x = output->x; crtc->y = output->y; crtc->rotation = output->rotation; crtc->mode_info = output->mode_info; copy_transform(&crtc->pending_transform, &output->transform); } if (!crtc->outputs) fatal("out of memory\n"); crtc->outputs[crtc->noutput++] = output; } static void set_crtcs(void) { output_t *output; for (output = all_outputs; output; output = output->next) { if (!output->mode_info) continue; crtc_add_output(output->crtc_info, output); } } static void set_panning(void) { output_t *output; for (output = all_outputs; output; output = output->next) { if (!output->crtc_info) continue; if (!(output->changes & changes_panning)) continue; if (!output->crtc_info->panning_info) output->crtc_info->panning_info = malloc(sizeof(XRRPanning)); memcpy(output->crtc_info->panning_info, &output->panning, sizeof(XRRPanning)); output->crtc_info->changing = 1; } } static void set_gamma(void) { output_t *output; for (output = all_outputs; output; output = output->next) { int i, size; crtc_t *crtc; XRRCrtcGamma *crtc_gamma; float gammaRed; float gammaGreen; float gammaBlue; if (!(output->changes & changes_gamma)) continue; if (!output->crtc_info) { fatal("Need crtc to set gamma on.\n"); continue; } crtc = output->crtc_info; size = XRRGetCrtcGammaSize(dpy, crtc->crtc.xid); if (!size) { fatal("Gamma size is 0.\n"); continue; } if (size > 65536) { fatal("Gamma correction table is impossibly large.\n"); continue; } crtc_gamma = XRRAllocGamma(size); if (!crtc_gamma) { fatal("Gamma allocation failed.\n"); continue; } if (output->gamma.red == 0.0) output->gamma.red = 1.0; if (output->gamma.green == 0.0) output->gamma.green = 1.0; if (output->gamma.blue == 0.0) output->gamma.blue = 1.0; gammaRed = 1.0 / output->gamma.red; gammaGreen = 1.0 / output->gamma.green; gammaBlue = 1.0 / output->gamma.blue; for (i = 0; i < size; i++) { if (gammaRed == 1.0 && output->brightness == 1.0) crtc_gamma->red[i] = (double) i / (double) (size - 1) * 65535.0; else crtc_gamma->red[i] = dmin(pow((double) i / (double) (size - 1), gammaRed) * output->brightness, 1.0) * 65535.0; if (gammaGreen == 1.0 && output->brightness == 1.0) crtc_gamma->green[i] = (double) i / (double) (size - 1) * 65535.0; else crtc_gamma->green[i] = dmin(pow((double) i / (double) (size - 1), gammaGreen) * output->brightness, 1.0) * 65535.0; if (gammaBlue == 1.0 && output->brightness == 1.0) crtc_gamma->blue[i] = (double) i / (double) (size - 1) * 65535.0; else crtc_gamma->blue[i] = dmin(pow((double) i / (double) (size - 1), gammaBlue) * output->brightness, 1.0) * 65535.0; } XRRSetCrtcGamma(dpy, crtc->crtc.xid, crtc_gamma); free(crtc_gamma); } } static void set_primary(void) { output_t *output; if (no_primary) { XRRSetOutputPrimary(dpy, root, None); } else { for (output = all_outputs; output; output = output->next) { if (!(output->changes & changes_primary)) continue; if (output->primary) XRRSetOutputPrimary(dpy, root, output->output.xid); } } } static Status crtc_disable(crtc_t *crtc) { if (verbose) printf("crtc %d: disable\n", crtc->crtc.index); if (dryrun) return RRSetConfigSuccess; return XRRSetCrtcConfig(dpy, res, crtc->crtc.xid, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); } static void crtc_set_transform(crtc_t *crtc, transform_t *transform) { int major, minor; XRRQueryVersion(dpy, &major, &minor); if (major > 1 || (major == 1 && minor >= 3)) XRRSetCrtcTransform(dpy, crtc->crtc.xid, &transform->transform, transform->filter, transform->params, transform->nparams); } static Status crtc_revert(crtc_t *crtc) { XRRCrtcInfo *crtc_info = crtc->crtc_info; if (verbose) printf("crtc %d: revert\n", crtc->crtc.index); if (dryrun) return RRSetConfigSuccess; if (!equal_transform(&crtc->current_transform, &crtc->pending_transform)) crtc_set_transform(crtc, &crtc->current_transform); 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->changing || !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) { printf("crtc %d: %12s %6.2f +%d+%d", crtc->crtc.index, crtc->mode_info->name, mode_refresh(crtc->mode_info), crtc->x, crtc->y); for (o = 0; o < crtc->noutput; o++) printf(" \"%s\"", crtc->outputs[o]->output.string); printf("\n"); } if (dryrun) s = RRSetConfigSuccess; else { if (!equal_transform(&crtc->current_transform, &crtc->pending_transform)) crtc_set_transform(crtc, &crtc->pending_transform); s = XRRSetCrtcConfig(dpy, res, crtc->crtc.xid, CurrentTime, crtc->x, crtc->y, mode, crtc->rotation, rr_outputs, crtc->noutput); if (s == RRSetConfigSuccess && crtc->panning_info) { if (has_1_3) s = XRRSetPanning(dpy, res, crtc->crtc.xid, crtc->panning_info); else fatal("panning needs RandR 1.3\n"); } } free(rr_outputs); return s; } static void screen_revert(void) { if (verbose) printf("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) printf("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; for (c = 0; c < res->ncrtc; c++) crtc_disable(&crtcs[c]); screen_revert(); for (c = 0; c < res->ncrtc; c++) crtc_revert(&crtcs[c]); } static void _X_NORETURN panic(Status s, crtc_t *crtc) { int c = crtc->crtc.index; const 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; if (grab_server) XGrabServer(dpy); for (c = 0; c < res->ncrtc; c++) { crtc_t *crtc = &crtcs[c]; XRRCrtcInfo *crtc_info = crtc->crtc_info; if (crtc_info->mode == None) continue; if (crtc->mode_info) { XRRModeInfo *old_mode = find_mode_by_xid(crtc_info->mode); int x, y, w, h; box_t bounds; if (!old_mode) panic(RRSetConfigFailed, crtc); mode_geometry(old_mode, crtc_info->rotation, &crtc->current_transform.transform, &bounds); x = crtc_info->x + bounds.x1; y = crtc_info->y + bounds.y1; w = bounds.x2 - bounds.x1; h = bounds.y2 - bounds.y1; if (x + w <= fb_width && y + h <= fb_height) continue; crtc->changing = True; } s = crtc_disable(crtc); if (s != RRSetConfigSuccess) panic(s, crtc); } screen_apply(); for (c = 0; c < res->ncrtc; c++) { crtc_t *crtc = &crtcs[c]; s = crtc_apply(crtc); if (s != RRSetConfigSuccess) panic(s, crtc); } set_primary(); if (grab_server) XUngrabServer(dpy); } static void get_outputs(void) { int o; output_t *q; 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%lx information\n", res->outputs[o]); init_name(&output_name); 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); if (automatic) { switch (output_info->connection) { case RR_Connected: if (!output_info->crtc) { output->changes |= changes_automatic; output->automatic = True; } break; case RR_Disconnected: if (output_info->crtc) { output->changes |= changes_automatic; output->automatic = True; } break; } } } output->found = True; if (output->automatic) { switch (output_info->connection) { case RR_Connected: case RR_UnknownConnection: if ((!(output->changes & changes_mode))) { set_name_preferred(&output->mode); output->changes |= changes_mode; } break; case RR_Disconnected: if ((!(output->changes & changes_mode))) { set_name_xid(&output->mode, None); set_name_xid(&output->crtc, None); output->changes |= changes_mode; output->changes |= changes_crtc; } break; } } set_output_info(output, res->outputs[o], output_info); } for (q = all_outputs; q; q = q->next) { if (!q->found) { fprintf(stderr, "warning: output %s not found; ignoring\n", q->output.string); } } } static void get_monitors(Bool get_active) { XRRMonitorInfo *m; int n; if (!has_1_5 || monitors) return; m = XRRGetMonitors(dpy, root, get_active, &n); if (n == -1) fatal("get monitors failed\n"); monitors = calloc(1, sizeof(monitors_t)); monitors->n = n; monitors->monitors = m; } static void mark_changing_crtcs(void) { int c; for (c = 0; c < num_crtcs; c++) { crtc_t *crtc = &crtcs[c]; int o; output_t *output; for (o = 0; o < crtc->crtc_info->noutput; o++) { output = find_output_by_xid(crtc->crtc_info->outputs[o]); if (!output) fatal("cannot find output 0x%lx\n", crtc->crtc_info->outputs[o]); if (output->changes) crtc->changing = True; } for (o = 0; o < crtc->noutput; o++) { output = crtc->outputs[o]; if (output->changes) crtc->changing = True; } } } static Bool check_crtc_for_output(crtc_t *crtc, output_t *output) { int c; int l; output_t *other; for (c = 0; c < output->output_info->ncrtc; c++) if (output->output_info->crtcs[c] == crtc->crtc.xid) break; if (c == output->output_info->ncrtc) return False; for (other = all_outputs; other; other = other->next) { if (other == output) continue; if (other->mode_info == NULL) continue; if (other->crtc_info != crtc) continue; for (l = 0; l < output->output_info->nclone; l++) if (output->output_info->clones[l] == other->output.xid) break; if (l == output->output_info->nclone) return False; } if (crtc->noutput) { 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; if (!equal_transform(&crtc->current_transform, &output->transform)) return False; } else if (crtc->crtc_info->noutput) { XRRModeInfo *mode = find_mode_by_xid(crtc->crtc_info->mode); if (mode != output->mode_info) return False; if (crtc->crtc_info->x != output->x) return False; if (crtc->crtc_info->y != output->y) return False; if (crtc->crtc_info->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%lx\n", output->output_info->crtcs[c]); if (check_crtc_for_output(crtc, output)) return crtc; } return NULL; } static void set_positions(void) { output_t *output; Bool keep_going; Bool any_set; int min_x, min_y; for (;;) { any_set = False; keep_going = False; for (output = all_outputs; output; output = output->next) { output_t *relation; name_t relation_name; if (!(output->changes & changes_relation)) continue; if (output->mode_info == NULL) continue; init_name(&relation_name); set_name_string(&relation_name, output->relative_to); relation = find_output(&relation_name); if (!relation) fatal("cannot find output \"%s\"\n", output->relative_to); if (relation->mode_info == NULL) { output->x = 0; output->y = 0; output->changes |= changes_position; any_set = True; continue; } if ((relation->changes & changes_relation) && !(relation->changes & changes_position)) { keep_going = True; continue; } switch (output->relation) { case relation_left_of: output->y = relation->y; output->x = relation->x - mode_width(output->mode_info, output->rotation); break; case relation_right_of: output->y = relation->y; output->x = relation->x + mode_width(relation->mode_info, relation->rotation); break; case relation_above: output->x = relation->x; output->y = relation->y - mode_height(output->mode_info, output->rotation); break; case relation_below: output->x = relation->x; output->y = relation->y + mode_height(relation->mode_info, relation->rotation); break; case relation_same_as: output->x = relation->x; output->y = relation->y; } output->changes |= changes_position; any_set = True; } if (!keep_going) break; if (!any_set) fatal("loop in relative position specifications\n"); } min_x = 32768; min_y = 32768; for (output = all_outputs; output; output = output->next) { 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) { for (output = all_outputs; output; output = output->next) { if (output->mode_info == NULL) continue; output->x -= min_x; output->y -= min_y; output->changes |= changes_position; } } } static void set_screen_size(void) { output_t *output; Bool fb_specified = fb_width != 0 && fb_height != 0; for (output = all_outputs; output; output = output->next) { XRRModeInfo *mode_info = output->mode_info; int x, y, w, h; box_t bounds; if (!mode_info) continue; mode_geometry(mode_info, output->rotation, &output->transform.transform, &bounds); x = output->x + bounds.x1; y = output->y + bounds.y1; w = bounds.x2 - bounds.x1; h = bounds.y2 - bounds.y1; if (fb_specified) { if (x + w > fb_width || y + h > fb_height) warning("specified screen %dx%d not large enough for output %s (%dx%d+%d+%d)\n", fb_width, fb_height, output->output.string, w, h, x, y); } else { XRRPanning *pan; if (x + w > fb_width) fb_width = x + w; if (y + h > fb_height) fb_height = y + h; if (output->changes & changes_panning) pan = &output->panning; else pan = output->crtc_info ? output->crtc_info->panning_info : NULL; if (pan && pan->left + pan->width > fb_width) fb_width = pan->left + pan->width; if (pan && pan->top + pan->height > fb_height) fb_height = pan->top + pan->height; } } if (fb_width > maxWidth || fb_height > maxHeight) fatal("screen cannot be larger than %dx%d (desired size %dx%d)\n", maxWidth, maxHeight, fb_width, fb_height); if (fb_specified) { if (fb_width < minWidth || fb_height < minHeight) fatal("screen must be at least %dx%d\n", minWidth, minHeight); } else { if (fb_width < minWidth) fb_width = minWidth; if (fb_height < minHeight) fb_height = minHeight; } } static void disable_outputs(output_t *outputs) { while (outputs) { outputs->crtc_info = NULL; outputs = outputs->next; } } 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; output->crtc_info = NULL; best_score = pick_crtcs_score(outputs); if (output->mode_info == NULL) return best_score; best_crtc = NULL; 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%lx\n", output->output_info->crtcs[c]); disable_outputs(outputs); if (!check_crtc_for_output(crtc, output)) continue; my_score = 1000; 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; (void) pick_crtcs_score(outputs); return best_score; } static void pick_crtcs(void) { output_t *output; int saved_crtc_noutput[num_crtcs]; int n; for (output = all_outputs; output; output = output->next) { if (output->changes && 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; } } } if (!output) return; for (output = all_outputs; output; output = output->next) output->current_crtc_info = output->crtc_info; for (n = 0; n < num_crtcs; n++) { saved_crtc_noutput[n] = crtcs[n].crtc_info->noutput; crtcs[n].crtc_info->noutput = 0; } pick_crtcs_score(all_outputs); for (n = 0; n < num_crtcs; n++) crtcs[n].crtc_info->noutput = saved_crtc_noutput[n]; for (output = all_outputs; output; output = output->next) { if (output->mode_info && !output->crtc_info) fatal("cannot find crtc for output %s\n", output->output.string); if (!output->changes && output->crtc_info != output->current_crtc_info) output->changes |= changes_crtc; } } void set_default_global() { fb_width_mm = 0; fb_height_mm = 0; all_outputs = NULL; all_outputs_tail = &all_outputs; crtcs = NULL; num_crtcs = 0; res = NULL; fb_width = 0, fb_height = 0; fb_width_mm = 0, fb_height_mm = 0; dpi = 0; dryrun = False; has_1_2 = False; has_1_3 = False; has_1_4 = False; has_1_5 = False; program_name = NULL; dpy = NULL; screen = -1; verbose = False; automatic = False; grab_server = True; no_primary = False; filter_type = -1; monitors = NULL; } typedef struct { char *first_monitor; char *second_monitor; int is_position; relation_t position; int primary; } x_change; int XChange(x_change *settings) { if (!settings->first_monitor) return 1; setenv("DISPLAY", ":0.0", 0); char *display_name = NULL; int event_base, error_base; output_t *config_output = NULL; int major, minor; config_output = find_output_by_name(settings->first_monitor); if (!config_output) { config_output = add_output(); set_name(&config_output->output, settings->first_monitor, name_string | name_xid); } if (settings->primary) { config_output->changes |= changes_primary; config_output->primary = True; } if (settings->is_position && settings->second_monitor) { config_output->relation = settings->position; config_output->relative_to = settings->second_monitor; config_output->changes |= changes_relation; } dpy = XOpenDisplay(display_name); if (dpy == NULL) { fprintf(stderr, "Can't open display %s\n", XDisplayName(display_name)); exit(1); } if (screen < 0) screen = DefaultScreen(dpy); if (screen >= ScreenCount(dpy)) { fprintf(stderr, "Invalid screen number %d (display has %d)\n", screen, ScreenCount(dpy)); exit(1); } root = RootWindow(dpy, screen); if (!XRRQueryExtension(dpy, &event_base, &error_base) || !XRRQueryVersion(dpy, &major, &minor)) { fprintf(stderr, "RandR extension missing\n"); exit(1); } if (major > 1 || (major == 1 && minor >= 2)) has_1_2 = True; if (major > 1 || (major == 1 && minor >= 3)) has_1_3 = True; if (major > 1 || (major == 1 && minor >= 4)) has_1_4 = True; if (major > 1 || (major == 1 && minor >= 5)) has_1_5 = True; get_screen(True); get_crtcs(); get_outputs(); set_positions(); set_screen_size(); pick_crtcs(); set_crtcs(); mark_changing_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); } } set_panning(); set_gamma(); apply(); XSync(dpy, False); XCloseDisplay(dpy); set_default_global(); return 0; } int XInfo(x_info *info) { setenv("DISPLAY", ":0.0", 0); char *display_name = NULL; int event_base, error_base; int major, minor; dpy = XOpenDisplay(display_name); if (dpy == NULL) { fprintf(stderr, "Can't open display %s\n", XDisplayName(display_name)); exit(1); } if (screen < 0) screen = DefaultScreen(dpy); if (screen >= ScreenCount(dpy)) { fprintf(stderr, "Invalid screen number %d (display has %d)\n", screen, ScreenCount(dpy)); exit(1); } root = RootWindow(dpy, screen); if (!XRRQueryExtension(dpy, &event_base, &error_base) || !XRRQueryVersion(dpy, &major, &minor)) { fprintf(stderr, "RandR extension missing\n"); exit(1); } if (major > 1 || (major == 1 && minor >= 2)) has_1_2 = True; if (major > 1 || (major == 1 && minor >= 3)) has_1_3 = True; if (major > 1 || (major == 1 && minor >= 4)) has_1_4 = True; if (major > 1 || (major == 1 && minor >= 5)) has_1_5 = True; if (!has_1_5) { printf("RandR 1.5 not supported\n"); exit(0); } get_screen(False); get_monitors(False); get_crtcs(); get_outputs(); if (monitors) { info->count = monitors->n; for (int m = 0; m < monitors->n; m++) { strcpy(info->monitor[m].name, XGetAtomName(dpy, monitors->monitors[m].name)); info->monitor[m].primary = monitors->monitors[m].primary ? 1 : 0; info->monitor[m].width = monitors->monitors[m].width; info->monitor[m].height = monitors->monitors[m].height; } XCloseDisplay(dpy); free(monitors); set_default_global(); return 0; } XCloseDisplay(dpy); set_default_global(); return 1; }