You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
8.7 KiB

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