i3lock-color

The world's most popular non-default computer lockscreen.
Index Commits Files Refs README LICENSE
randr.c (8930B)
   1 /*
   2  * vim:ts=4:sw=4:expandtab
   3  *
   4  * © 2010 Michael Stapelberg
   5  * © 2021 Raymond Li
   6  *
   7  * See LICENSE for licensing information
   8  *
   9  */
  10 #include <stdbool.h>
  11 #include <stdlib.h>
  12 #include <stdio.h>
  13 #include <math.h>
  14 #include <xcb/xcb.h>
  15 #include <xcb/xinerama.h>
  16 #include <xcb/randr.h>
  17 
  18 #include "i3lock.h"
  19 #include "xcb.h"
  20 #include "randr.h"
  21 
  22 /* Number of Xinerama screens which are currently present. */
  23 int xr_screens = 0;
  24 
  25 /* The resolutions of the currently present Xinerama screens. */
  26 Rect *xr_resolutions = NULL;
  27 
  28 static bool xinerama_active;
  29 static bool has_randr = false;
  30 static bool has_randr_1_5 = false;
  31 extern bool debug_mode;
  32 
  33 void _xinerama_init(void);
  34 
  35 void randr_init(int *event_base, xcb_window_t root) {
  36     const xcb_query_extension_reply_t *extreply;
  37 
  38     extreply = xcb_get_extension_data(conn, &xcb_randr_id);
  39     if (!extreply->present) {
  40         DEBUG("RandR is not present, falling back to Xinerama.\n");
  41         _xinerama_init();
  42         return;
  43     }
  44 
  45     xcb_generic_error_t *err;
  46     xcb_randr_query_version_reply_t *randr_version =
  47         xcb_randr_query_version_reply(
  48             conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
  49     if (err != NULL) {
  50         DEBUG("Could not query RandR version: X11 error code %d\n", err->error_code);
  51         _xinerama_init();
  52         return;
  53     }
  54 
  55     has_randr = true;
  56 
  57     has_randr_1_5 = (randr_version->major_version >= 1) &&
  58                     (randr_version->minor_version >= 5);
  59 
  60     free(randr_version);
  61 
  62     if (event_base != NULL)
  63         *event_base = extreply->first_event;
  64 
  65     xcb_randr_select_input(conn, root,
  66                            XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
  67                                XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
  68                                XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
  69                                XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
  70 
  71     xcb_flush(conn);
  72 }
  73 
  74 void _xinerama_init(void) {
  75     if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
  76         DEBUG("Xinerama extension not found, disabling.\n");
  77         return;
  78     }
  79 
  80     xcb_xinerama_is_active_cookie_t cookie;
  81     xcb_xinerama_is_active_reply_t *reply;
  82 
  83     cookie = xcb_xinerama_is_active(conn);
  84     reply = xcb_xinerama_is_active_reply(conn, cookie, NULL);
  85     if (!reply)
  86         return;
  87 
  88     if (!reply->state) {
  89         free(reply);
  90         return;
  91     }
  92 
  93     xinerama_active = true;
  94     free(reply);
  95 }
  96 
  97 /*
  98  * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
  99  *
 100  */
 101 static bool _randr_query_monitors_15(xcb_window_t root) {
 102 #if XCB_RANDR_MINOR_VERSION < 5
 103     return false;
 104 #else
 105     /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
 106     if (!has_randr_1_5) {
 107         return false;
 108     }
 109     /* RandR 1.5 available at run-time (supported by the server) */
 110     DEBUG("Querying monitors using RandR 1.5\n");
 111     xcb_generic_error_t *err;
 112     xcb_randr_get_monitors_reply_t *monitors =
 113         xcb_randr_get_monitors_reply(
 114             conn, xcb_randr_get_monitors(conn, root, true), &err);
 115     if (err != NULL) {
 116         DEBUG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
 117         free(err);
 118         /* Fall back to RandR ≤ 1.4 */
 119         return false;
 120     }
 121 
 122     int screens = xcb_randr_get_monitors_monitors_length(monitors);
 123     DEBUG("%d RandR monitors found (timestamp %d)\n",
 124           screens, monitors->timestamp);
 125 
 126     Rect *resolutions = malloc(screens * sizeof(Rect));
 127     /* No memory? Just keep on using the old information. */
 128     if (!resolutions) {
 129         free(monitors);
 130         return true;
 131     }
 132 
 133     xcb_randr_monitor_info_iterator_t iter;
 134     int screen;
 135     for (iter = xcb_randr_get_monitors_monitors_iterator(monitors), screen = 0;
 136          iter.rem;
 137          xcb_randr_monitor_info_next(&iter), screen++) {
 138         const xcb_randr_monitor_info_t *monitor_info = iter.data;
 139 
 140         resolutions[screen].x = monitor_info->x;
 141         resolutions[screen].y = monitor_info->y;
 142         resolutions[screen].width = monitor_info->width;
 143         resolutions[screen].height = monitor_info->height;
 144         DEBUG("found RandR monitor: %d x %d at %d x %d\n",
 145               monitor_info->width, monitor_info->height,
 146               monitor_info->x, monitor_info->y);
 147     }
 148     free(xr_resolutions);
 149     xr_resolutions = resolutions;
 150     xr_screens = screens;
 151 
 152     free(monitors);
 153     return true;
 154 #endif
 155 }
 156 
 157 /*
 158  * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
 159  *
 160  */
 161 static bool _randr_query_outputs_14(xcb_window_t root) {
 162     if (!has_randr) {
 163         return false;
 164     }
 165     DEBUG("Querying outputs using RandR ≤ 1.4\n");
 166 
 167     /* Get screen resources (primary output, crtcs, outputs, modes) */
 168     xcb_randr_get_screen_resources_current_cookie_t rcookie;
 169     rcookie = xcb_randr_get_screen_resources_current(conn, root);
 170 
 171     xcb_randr_get_screen_resources_current_reply_t *res =
 172         xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
 173     if (res == NULL) {
 174         DEBUG("Could not query screen resources.\n");
 175         return false;
 176     }
 177 
 178     /* timestamp of the configuration so that we get consistent replies to all
 179      * requests (if the configuration changes between our different calls) */
 180     const xcb_timestamp_t cts = res->config_timestamp;
 181 
 182     const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
 183 
 184     /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
 185     xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
 186 
 187     /* Request information for each output */
 188     xcb_randr_get_output_info_cookie_t ocookie[len];
 189     for (int i = 0; i < len; i++) {
 190         ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
 191     }
 192     Rect *resolutions = malloc(len * sizeof(Rect));
 193     /* No memory? Just keep on using the old information. */
 194     if (!resolutions) {
 195         free(res);
 196         return true;
 197     }
 198 
 199     /* Loop through all outputs available for this X11 screen */
 200     int screen = 0;
 201 
 202     for (int i = 0; i < len; i++) {
 203         xcb_randr_get_output_info_reply_t *output;
 204 
 205         if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) {
 206             continue;
 207         }
 208 
 209         if (output->crtc == XCB_NONE) {
 210             free(output);
 211             continue;
 212         }
 213 
 214         xcb_randr_get_crtc_info_cookie_t icookie;
 215         xcb_randr_get_crtc_info_reply_t *crtc;
 216         icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
 217         if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
 218             DEBUG("Skipping output: could not get CRTC (0x%08x)\n", output->crtc);
 219             free(output);
 220             continue;
 221         }
 222 
 223         resolutions[screen].x = crtc->x;
 224         resolutions[screen].y = crtc->y;
 225         resolutions[screen].width = crtc->width;
 226         resolutions[screen].height = crtc->height;
 227 
 228         DEBUG("found RandR output: %d x %d at %d x %d\n",
 229               crtc->width, crtc->height,
 230               crtc->x, crtc->y);
 231 
 232         screen++;
 233 
 234         free(crtc);
 235 
 236         free(output);
 237     }
 238     free(xr_resolutions);
 239     xr_resolutions = resolutions;
 240     xr_screens = screen;
 241     free(res);
 242     return true;
 243 }
 244 
 245 void _xinerama_query_screens(void) {
 246     if (!xinerama_active) {
 247         return;
 248     }
 249 
 250     xcb_xinerama_query_screens_cookie_t cookie;
 251     xcb_xinerama_query_screens_reply_t *reply;
 252     xcb_xinerama_screen_info_t *screen_info;
 253     xcb_generic_error_t *err;
 254     cookie = xcb_xinerama_query_screens_unchecked(conn);
 255     reply = xcb_xinerama_query_screens_reply(conn, cookie, &err);
 256     if (!reply) {
 257         DEBUG("Couldn't get Xinerama screens: X11 error code %d\n", err->error_code);
 258         free(err);
 259         return;
 260     }
 261     screen_info = xcb_xinerama_query_screens_screen_info(reply);
 262     int screens = xcb_xinerama_query_screens_screen_info_length(reply);
 263 
 264     Rect *resolutions = malloc(screens * sizeof(Rect));
 265     /* No memory? Just keep on using the old information. */
 266     if (!resolutions) {
 267         free(reply);
 268         return;
 269     }
 270 
 271     for (int screen = 0; screen < xr_screens; screen++) {
 272         resolutions[screen].x = screen_info[screen].x_org;
 273         resolutions[screen].y = screen_info[screen].y_org;
 274         resolutions[screen].width = screen_info[screen].width;
 275         resolutions[screen].height = screen_info[screen].height;
 276         DEBUG("found Xinerama screen: %d x %d at %d x %d\n",
 277               screen_info[screen].width, screen_info[screen].height,
 278               screen_info[screen].x_org, screen_info[screen].y_org);
 279     }
 280 
 281     free(xr_resolutions);
 282     xr_resolutions = resolutions;
 283     xr_screens = screens;
 284 
 285     free(reply);
 286 }
 287 
 288 void randr_query(xcb_window_t root) {
 289     if (_randr_query_monitors_15(root)) {
 290         return;
 291     }
 292 
 293     if (_randr_query_outputs_14(root)) {
 294         return;
 295     }
 296 
 297     _xinerama_query_screens();
 298 }