From 61df35ee17ce6826da3535c3a23ceaed9c0d3726 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Fri, 20 May 2022 16:28:04 +0300 Subject: [PATCH] first commit --- .gitignore | 2 + README.md | 40 + dub.json | 27 + dub.selections.json | 5 + dub.settings.json | 4 + source/app.d | 28 + source/clang/monitors.c | 2150 +++++++++++++++++++++++++++++++++++++ source/modules/monitors.d | 96 ++ 8 files changed, 2352 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 dub.json create mode 100644 dub.selections.json create mode 100644 dub.settings.json create mode 100644 source/app.d create mode 100644 source/clang/monitors.c create mode 100644 source/modules/monitors.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..742c2f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +mswitch diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa27ff8 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Monitor switch (based on XrandR) + +Switching occurs only if 2 monitors are connected to the computer! + +## dub + +- [Introduction](https://dub.pm/package-format-json) +- [General usage](https://dub.pm/commandline.html) +- [Configuring default settings](https://dub.pm/settings) + +## How to build + +Install `gcc` and `X11` development libraries. Also install `dmd` or `ldc` compiler. Run: + +```sh +git clone https://git.zhirov.website/alexander/mswitch.git +cd mswitch +dub +``` + +or + +```sh +dub -- ~/mswitch.log +``` + +Something similar will be output to the log file `mswitch.log`, and the monitors will be changed: + +```sh +[Monitor("eDP-1", true, 1366, 768), Monitor("HDMI-1", false, 1920, 1080)] +-- Switch monitors -- +[Monitor("HDMI-1", true, 1920, 1080), Monitor("eDP-1", false, 1366, 768)] +``` + +where first (or second) line: + +- `eDP-1` - output +- `true` - is primary +- `1366` - width +- `768` - height diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..818a2d2 --- /dev/null +++ b/dub.json @@ -0,0 +1,27 @@ +{ + "name": "mswitch", + "authors": [ + "Alexander Zhirov" + ], + "homepage": "https://git.zhirov.website/alexander/mswitcher", + "license": "GPL-2.0", + "copyright": "Copyright © 2022, Alexander Zhirov", + "description": "Monitor switcher, based on XrandR", + "dflags": [ + "-i" + ], + "libs": [ + "X11", + "Xrandr", + "m" + ], + "preBuildCommands": [ + "gcc -Os -c source/clang/monitors.c -o obj/monitors.o" + ], + "sourceFiles": [ + "obj/*.o" + ], + "targetType": "executable", + "targetPath": "bin", + "targetName": "mswitch" +} diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..322586b --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,5 @@ +{ + "fileVersion": 1, + "versions": { + } +} diff --git a/dub.settings.json b/dub.settings.json new file mode 100644 index 0000000..9da886c --- /dev/null +++ b/dub.settings.json @@ -0,0 +1,4 @@ +{ + "defaultArchitecture": "x86_64", + "defaultCompiler": "ldc2" +} diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..9b4f755 --- /dev/null +++ b/source/app.d @@ -0,0 +1,28 @@ +import std.stdio; +import modules.monitors; + +/** + * Switching occurs only if 2 monitors are connected to the computer! + */ + +int main(string[] args) +{ + string path = "mswitch.log"; + if (args.length > 1) + { + path = args[1]; + } + auto file = File(path, "w"); + + auto monitors = getMonitorsInfo(); + file.writeln(monitors); + setPrimaryMonitor(monitors[1].name); + file.writeln("-- Switch monitors --"); + swapMonitors(monitors[0].name, monitors[1].name, Relation.right_of); + monitors = getMonitorsInfo(); + file.writeln(monitors); + + file.close(); + + return 0; +} diff --git a/source/clang/monitors.c b/source/clang/monitors.c new file mode 100644 index 0000000..67716c4 --- /dev/null +++ b/source/clang/monitors.c @@ -0,0 +1,2150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + } +} + +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); + + return 0; +} + +typedef struct +{ + char name[10]; + int width; + int height; + int primary; +} x_monitor; + +typedef struct +{ + int count; + x_monitor monitor[5]; +} x_info; + +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); + monitors = NULL; + + return 0; + } + + XCloseDisplay(dpy); + + return 1; +} + +// void XPrint(const x_info *info) +// { +// for (int i = 0; i < info->count; ++i) +// { +// printf("%d %s %d %d\n", +// info->monitor[i].primary, +// info->monitor[i].name, +// info->monitor[i].width, +// info->monitor[i].height); +// } +// } + +// int main() +// { +// x_change settings; +// settings.first_monitor = "HDMI-1"; +// // settings.first_monitor = "eDP-1"; +// // settings.second_monitor = "HDMI-1"; +// // settings.position = relation_right_of; +// // settings.is_position = True; +// settings.is_position = False; +// settings.primary = True; + +// x_info i; + +// XInfo(&i); +// XPrint(&i); +// XChange(&settings); +// printf("switch\n"); +// XInfo(&i); +// XPrint(&i); + +// return 0; +// } diff --git a/source/modules/monitors.d b/source/modules/monitors.d new file mode 100644 index 0000000..4fb6b6e --- /dev/null +++ b/source/modules/monitors.d @@ -0,0 +1,96 @@ +module modules.monitors; + +import std.conv; +import std.string; + +extern (C) +{ + enum Relation + { + left_of, + right_of, + above, + below, + same_as, + } + + struct x_change + { + immutable(char)* first_monitor; + immutable(char)* second_monitor; + int is_position; + Relation position; + int primary; + } + + struct x_monitor + { + char[10] name = "\0"; + int width; + int height; + int primary; + } + + struct x_info + { + int count; + x_monitor[5] monitor; + } + + int XInfo(x_info* info); + int XChange(x_change* settings); +} + +struct Monitor +{ + string name; + bool primary; + int width; + int height; +} + +Monitor[] getMonitorsInfo() +{ + Monitor[] m; + + x_info monitors; + if (!XInfo(&monitors)) + { + foreach (i; 0 .. monitors.count) + { + Monitor current; + current.name = fromStringz(monitors.monitor[i].name.idup); + current.primary = to!bool(monitors.monitor[i].primary); + current.width = monitors.monitor[i].width; + current.height = monitors.monitor[i].height; + + m ~= current; + } + } + + return m; +} + +bool setPrimaryMonitor(string monitor) +{ + x_change settings; + + settings.first_monitor = toStringz(monitor); + settings.primary = 1; + settings.is_position = 0; + + return to!bool(XChange(&settings)); +} + +bool swapMonitors(string first, string second, Relation position) +{ + x_change settings; + + settings.first_monitor = toStringz(first); + settings.second_monitor = toStringz(second); + settings.is_position = 1; + settings.position = position; + settings.primary = 0; + + return to!bool(XChange(&settings)); +}