i3
randr.c
Go to the documentation of this file.
1 #undef I3__FILE__
2 #define I3__FILE__ "randr.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * For more information on RandR, please see the X.org RandR specification at
10  * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
11  * (take your time to read it completely, it answers all questions).
12  *
13  */
14 #include "all.h"
15 
16 #include <time.h>
17 #include <xcb/randr.h>
18 
19 /* While a clean namespace is usually a pretty good thing, we really need
20  * to use shorter names than the whole xcb_randr_* default names. */
21 typedef xcb_randr_get_crtc_info_reply_t crtc_info;
22 typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
23 
24 /* Pointer to the result of the query for primary output */
25 xcb_randr_get_output_primary_reply_t *primary;
26 
27 /* Stores all outputs available in your current session. */
28 struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
29 
30 static bool randr_disabled = false;
31 
32 /*
33  * Get a specific output by its internal X11 id. Used by randr_query_outputs
34  * to check if the output is new (only in the first scan) or if we are
35  * re-scanning.
36  *
37  */
38 static Output *get_output_by_id(xcb_randr_output_t id) {
39  Output *output;
40  TAILQ_FOREACH(output, &outputs, outputs)
41  if (output->id == id)
42  return output;
43 
44  return NULL;
45 }
46 
47 /*
48  * Returns the output with the given name if it is active (!) or NULL.
49  *
50  */
51 Output *get_output_by_name(const char *name) {
52  Output *output;
53  TAILQ_FOREACH(output, &outputs, outputs)
54  if (output->active &&
55  strcasecmp(output->name, name) == 0)
56  return output;
57 
58  return NULL;
59 }
60 
61 /*
62  * Returns the first output which is active.
63  *
64  */
66  Output *output;
67 
68  TAILQ_FOREACH(output, &outputs, outputs)
69  if (output->active)
70  return output;
71 
72  die("No usable outputs available.\n");
73 }
74 
75 /*
76  * Returns the active (!) output which contains the coordinates x, y or NULL
77  * if there is no output which contains these coordinates.
78  *
79  */
81  Output *output;
82  TAILQ_FOREACH(output, &outputs, outputs) {
83  if (!output->active)
84  continue;
85  DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
86  x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
87  if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
88  y >= output->rect.y && y < (output->rect.y + output->rect.height))
89  return output;
90  }
91 
92  return NULL;
93 }
94 
95 /*
96  * In contained_by_output, we check if any active output contains part of the container.
97  * We do this by checking if the output rect is intersected by the Rect.
98  * This is the 2-dimensional counterpart of get_output_containing.
99  * Since we don't actually need the outputs intersected by the given Rect (There could
100  * be many), we just return true or false for convenience.
101  *
102  */
104  Output *output;
105  int lx = rect.x, uy = rect.y;
106  int rx = rect.x + rect.width, by = rect.y + rect.height;
107  TAILQ_FOREACH(output, &outputs, outputs) {
108  if (!output->active)
109  continue;
110  DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
111  rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
112  if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
113  by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
114  return true;
115  }
116  return false;
117 
118 }
119 
120 /*
121  * Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
122  *
123  * For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
124  * get_output_next_wrap(D_DOWN, x) will return the topmost output.
125  *
126  * This function always returns a output: if no active outputs can be found,
127  * current itself is returned.
128  *
129  */
131  Output *best = get_output_next(direction, current, CLOSEST_OUTPUT);
132  /* If no output can be found, wrap */
133  if (!best) {
134  direction_t opposite;
135  if (direction == D_RIGHT)
136  opposite = D_LEFT;
137  else if (direction == D_LEFT)
138  opposite = D_RIGHT;
139  else if (direction == D_DOWN)
140  opposite = D_UP;
141  else
142  opposite = D_DOWN;
143  best = get_output_next(opposite, current, FARTHEST_OUTPUT);
144  }
145  if (!best)
146  best = current;
147  DLOG("current = %s, best = %s\n", current->name, best->name);
148  return best;
149 }
150 
151 /*
152  * Gets the output which is the next one in the given direction.
153  *
154  * If close_far == CLOSEST_OUTPUT, then the output next to the current one will
155  * selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
156  * in the given direction will be selected.
157  *
158  * NULL will be returned when no active outputs are present in the direction
159  * specified (note that “current” counts as such an output).
160  *
161  */
162 Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
163  Rect *cur = &(current->rect),
164  *other;
165  Output *output,
166  *best = NULL;
167  TAILQ_FOREACH(output, &outputs, outputs) {
168  if (!output->active)
169  continue;
170 
171  other = &(output->rect);
172 
173  if ((direction == D_RIGHT && other->x > cur->x) ||
174  (direction == D_LEFT && other->x < cur->x)) {
175  /* Skip the output when it doesn’t overlap the other one’s y
176  * coordinate at all. */
177  if ((other->y + other->height) <= cur->y ||
178  (cur->y + cur->height) <= other->y)
179  continue;
180  } else if ((direction == D_DOWN && other->y > cur->y) ||
181  (direction == D_UP && other->y < cur->y)) {
182  /* Skip the output when it doesn’t overlap the other one’s x
183  * coordinate at all. */
184  if ((other->x + other->width) <= cur->x ||
185  (cur->x + cur->width) <= other->x)
186  continue;
187  } else
188  continue;
189 
190  /* No candidate yet? Start with this one. */
191  if (!best) {
192  best = output;
193  continue;
194  }
195 
196  if (close_far == CLOSEST_OUTPUT) {
197  /* Is this output better (closer to the current output) than our
198  * current best bet? */
199  if ((direction == D_RIGHT && other->x < best->rect.x) ||
200  (direction == D_LEFT && other->x > best->rect.x) ||
201  (direction == D_DOWN && other->y < best->rect.y) ||
202  (direction == D_UP && other->y > best->rect.y)) {
203  best = output;
204  continue;
205  }
206  } else {
207  /* Is this output better (farther to the current output) than our
208  * current best bet? */
209  if ((direction == D_RIGHT && other->x > best->rect.x) ||
210  (direction == D_LEFT && other->x < best->rect.x) ||
211  (direction == D_DOWN && other->y > best->rect.y) ||
212  (direction == D_UP && other->y < best->rect.y)) {
213  best = output;
214  continue;
215  }
216  }
217  }
218 
219  DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
220  return best;
221 }
222 
223 /*
224  * Disables RandR support by creating exactly one output with the size of the
225  * X11 screen.
226  *
227  */
228 void disable_randr(xcb_connection_t *conn) {
229  DLOG("RandR extension unusable, disabling.\n");
230 
231  Output *s = scalloc(sizeof(Output));
232 
233  s->active = true;
234  s->rect.x = 0;
235  s->rect.y = 0;
236  s->rect.width = root_screen->width_in_pixels;
237  s->rect.height = root_screen->height_in_pixels;
238  s->name = "xroot-0";
239  output_init_con(s);
241 
243 
244  randr_disabled = true;
245 }
246 
247 /*
248  * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
249  * before) to use for the given Output.
250  *
251  */
252 void output_init_con(Output *output) {
253  Con *con = NULL, *current;
254  bool reused = false;
255 
256  DLOG("init_con for output %s\n", output->name);
257 
258  /* Search for a Con with that name directly below the root node. There
259  * might be one from a restored layout. */
260  TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
261  if (strcmp(current->name, output->name) != 0)
262  continue;
263 
264  con = current;
265  reused = true;
266  DLOG("Using existing con %p / %s\n", con, con->name);
267  break;
268  }
269 
270  if (con == NULL) {
271  con = con_new(croot, NULL);
272  FREE(con->name);
273  con->name = sstrdup(output->name);
274  con->type = CT_OUTPUT;
275  con->layout = L_OUTPUT;
277  }
278  con->rect = output->rect;
279  output->con = con;
280 
281  char *name;
282  sasprintf(&name, "[i3 con] output %s", con->name);
283  x_set_name(con, name);
284  FREE(name);
285 
286  if (reused) {
287  DLOG("Not adding workspace, this was a reused con\n");
288  return;
289  }
290 
291  DLOG("Changing layout, adding top/bottom dockarea\n");
292  Con *topdock = con_new(NULL, NULL);
293  topdock->type = CT_DOCKAREA;
294  topdock->layout = L_DOCKAREA;
295  /* this container swallows dock clients */
296  Match *match = scalloc(sizeof(Match));
297  match_init(match);
298  match->dock = M_DOCK_TOP;
299  match->insert_where = M_BELOW;
300  TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
301 
302  FREE(topdock->name);
303  topdock->name = sstrdup("topdock");
304 
305  sasprintf(&name, "[i3 con] top dockarea %s", con->name);
306  x_set_name(topdock, name);
307  FREE(name);
308  DLOG("attaching\n");
309  con_attach(topdock, con, false);
310 
311  /* content container */
312 
313  DLOG("adding main content container\n");
314  Con *content = con_new(NULL, NULL);
315  content->type = CT_CON;
316  content->layout = L_SPLITH;
317  FREE(content->name);
318  content->name = sstrdup("content");
319 
320  sasprintf(&name, "[i3 con] content %s", con->name);
321  x_set_name(content, name);
322  FREE(name);
323  con_attach(content, con, false);
324 
325  /* bottom dock container */
326  Con *bottomdock = con_new(NULL, NULL);
327  bottomdock->type = CT_DOCKAREA;
328  bottomdock->layout = L_DOCKAREA;
329  /* this container swallows dock clients */
330  match = scalloc(sizeof(Match));
331  match_init(match);
332  match->dock = M_DOCK_BOTTOM;
333  match->insert_where = M_BELOW;
334  TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches);
335 
336  FREE(bottomdock->name);
337  bottomdock->name = sstrdup("bottomdock");
338 
339  sasprintf(&name, "[i3 con] bottom dockarea %s", con->name);
340  x_set_name(bottomdock, name);
341  FREE(name);
342  DLOG("attaching\n");
343  con_attach(bottomdock, con, false);
344 }
345 
346 /*
347  * Initializes at least one workspace for this output, trying the following
348  * steps until there is at least one workspace:
349  *
350  * • Move existing workspaces, which are assigned to be on the given output, to
351  * the output.
352  * • Create the first assigned workspace for this output.
353  * • Create the first unused workspace.
354  *
355  */
356 void init_ws_for_output(Output *output, Con *content) {
357  /* go through all assignments and move the existing workspaces to this output */
358  struct Workspace_Assignment *assignment;
360  if (strcmp(assignment->output, output->name) != 0)
361  continue;
362 
363  /* check if this workspace actually exists */
364  Con *workspace = NULL, *out;
365  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
366  GREP_FIRST(workspace, output_get_content(out),
367  !strcasecmp(child->name, assignment->name));
368  if (workspace == NULL)
369  continue;
370 
371  /* check that this workspace is not already attached (that means the
372  * user configured this assignment twice) */
373  Con *workspace_out = con_get_output(workspace);
374  if (workspace_out == output->con) {
375  LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
376  "there. Do you have two assignment directives for the same "
377  "workspace in your configuration file?\n",
378  workspace->name, output->name);
379  continue;
380  }
381 
382  /* if so, move it over */
383  LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
384  workspace->name, workspace_out->name, output->name);
385 
386  /* if the workspace is currently visible on that output, we need to
387  * switch to a different workspace - otherwise the output would end up
388  * with no active workspace */
389  bool visible = workspace_is_visible(workspace);
390  Con *previous = NULL;
391  if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
392  LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
393  previous->name, workspace_out->name);
394  workspace_show(previous);
395  }
396 
397  /* Render the output on which the workspace was to get correct Rects.
398  * Then, we need to work with the "content" container, since we cannot
399  * be sure that the workspace itself was rendered at all (in case it’s
400  * invisible, it won’t be rendered). */
401  render_con(workspace_out, false);
402  Con *ws_out_content = output_get_content(workspace_out);
403 
404  Con *floating_con;
405  TAILQ_FOREACH(floating_con, &(workspace->floating_head), floating_windows)
406  /* NB: We use output->con here because content is not yet rendered,
407  * so it has a rect of {0, 0, 0, 0}. */
408  floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect));
409 
410  con_detach(workspace);
411  con_attach(workspace, content, false);
412 
413  /* In case the workspace we just moved was visible but there was no
414  * other workspace to switch to, we need to initialize the source
415  * output aswell */
416  if (visible && previous == NULL) {
417  LOG("There is no workspace left on \"%s\", re-initializing\n",
418  workspace_out->name);
420  output_get_content(workspace_out));
421  DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
422  }
423  }
424 
425  /* if a workspace exists, we are done now */
426  if (!TAILQ_EMPTY(&(content->nodes_head))) {
427  /* ensure that one of the workspaces is actually visible (in fullscreen
428  * mode), if they were invisible before, this might not be the case. */
429  Con *visible = NULL;
430  GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
431  if (!visible) {
432  visible = TAILQ_FIRST(&(content->nodes_head));
433  focused = content;
434  workspace_show(visible);
435  }
436  return;
437  }
438 
439  /* otherwise, we create the first assigned ws for this output */
441  if (strcmp(assignment->output, output->name) != 0)
442  continue;
443 
444  LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
445  assignment->name, assignment->output);
446  focused = content;
447  workspace_show_by_name(assignment->name);
448  return;
449  }
450 
451  /* if there is still no workspace, we create the first free workspace */
452  DLOG("Now adding a workspace\n");
453  Con *ws = create_workspace_on_output(output, content);
454 
455  /* TODO: Set focus in main.c */
456  con_focus(ws);
457 }
458 
459 /*
460  * This function needs to be called when changing the mode of an output when
461  * it already has some workspaces (or a bar window) assigned.
462  *
463  * It reconfigures the bar window for the new mode, copies the new rect into
464  * each workspace on this output and forces all windows on the affected
465  * workspaces to be reconfigured.
466  *
467  * It is necessary to call render_layout() afterwards.
468  *
469  */
470 static void output_change_mode(xcb_connection_t *conn, Output *output) {
471  DLOG("Output mode changed, updating rect\n");
472  assert(output->con != NULL);
473  output->con->rect = output->rect;
474 
475  Con *content, *workspace, *child;
476 
477  /* Point content to the container of the workspaces */
478  content = output_get_content(output->con);
479 
480  /* Fix the position of all floating windows on this output.
481  * The 'rect' of each workspace will be updated in src/render.c. */
482  TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
483  TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
484  floating_fix_coordinates(child, &(workspace->rect), &(output->con->rect));
485  }
486  }
487 
488  /* If default_orientation is NO_ORIENTATION, we change the orientation of
489  * the workspaces and their childs depending on output resolution. This is
490  * only done for workspaces with maximum one child. */
492  TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
493  /* Workspaces with more than one child are left untouched because
494  * we do not want to change an existing layout. */
495  if (con_num_children(workspace) > 1)
496  continue;
497 
498  workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
499  DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout);
500  if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
501  if (child->layout == L_SPLITV || child->layout == L_SPLITH)
502  child->layout = workspace->layout;
503  DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout);
504  }
505  }
506  }
507 }
508 
509 /*
510  * Gets called by randr_query_outputs() for each output. The function adds new
511  * outputs to the list of outputs, checks if the mode of existing outputs has
512  * been changed or if an existing output has been disabled. It will then change
513  * either the "changed" or the "to_be_deleted" flag of the output, if
514  * appropriate.
515  *
516  */
517 static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
518  xcb_randr_get_output_info_reply_t *output,
519  xcb_timestamp_t cts, resources_reply *res) {
520  /* each CRT controller has a position in which we are interested in */
521  crtc_info *crtc;
522 
523  Output *new = get_output_by_id(id);
524  bool existing = (new != NULL);
525  if (!existing)
526  new = scalloc(sizeof(Output));
527  new->id = id;
528  new->primary = (primary && primary->output == id);
529  FREE(new->name);
530  sasprintf(&new->name, "%.*s",
531  xcb_randr_get_output_info_name_length(output),
532  xcb_randr_get_output_info_name(output));
533 
534  DLOG("found output with name %s\n", new->name);
535 
536  /* Even if no CRTC is used at the moment, we store the output so that
537  * we do not need to change the list ever again (we only update the
538  * position/size) */
539  if (output->crtc == XCB_NONE) {
540  if (!existing) {
541  if (new->primary)
543  else TAILQ_INSERT_TAIL(&outputs, new, outputs);
544  } else if (new->active)
545  new->to_be_disabled = true;
546  return;
547  }
548 
549  xcb_randr_get_crtc_info_cookie_t icookie;
550  icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
551  if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
552  DLOG("Skipping output %s: could not get CRTC (%p)\n",
553  new->name, crtc);
554  free(new);
555  return;
556  }
557 
558  bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
559  update_if_necessary(&(new->rect.y), crtc->y) |
560  update_if_necessary(&(new->rect.width), crtc->width) |
561  update_if_necessary(&(new->rect.height), crtc->height);
562  free(crtc);
563  new->active = (new->rect.width != 0 && new->rect.height != 0);
564  if (!new->active) {
565  DLOG("width/height 0/0, disabling output\n");
566  return;
567  }
568 
569  DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
570  new->rect.x, new->rect.y);
571 
572  /* If we don’t need to change an existing output or if the output
573  * does not exist in the first place, the case is simple: we either
574  * need to insert the new output or we are done. */
575  if (!updated || !existing) {
576  if (!existing) {
577  if (new->primary)
579  else TAILQ_INSERT_TAIL(&outputs, new, outputs);
580  }
581  return;
582  }
583 
584  new->changed = true;
585 }
586 
587 /*
588  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
589  *
590  */
592  Output *output, *other, *first;
593  xcb_randr_get_output_primary_cookie_t pcookie;
594  xcb_randr_get_screen_resources_current_cookie_t rcookie;
595  resources_reply *res;
596 
597  /* timestamp of the configuration so that we get consistent replies to all
598  * requests (if the configuration changes between our different calls) */
599  xcb_timestamp_t cts;
600 
601  /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
602  xcb_randr_output_t *randr_outputs;
603 
604  if (randr_disabled)
605  return;
606 
607  /* Get screen resources (primary output, crtcs, outputs, modes) */
608  rcookie = xcb_randr_get_screen_resources_current(conn, root);
609  pcookie = xcb_randr_get_output_primary(conn, root);
610 
611  if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
612  ELOG("Could not get RandR primary output\n");
613  else DLOG("primary output is %08x\n", primary->output);
614  if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
616  return;
617  }
618  cts = res->config_timestamp;
619 
620  int len = xcb_randr_get_screen_resources_current_outputs_length(res);
621  randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
622 
623  /* Request information for each output */
624  xcb_randr_get_output_info_cookie_t ocookie[len];
625  for (int i = 0; i < len; i++)
626  ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
627 
628  /* Loop through all outputs available for this X11 screen */
629  for (int i = 0; i < len; i++) {
630  xcb_randr_get_output_info_reply_t *output;
631 
632  if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
633  continue;
634 
635  handle_output(conn, randr_outputs[i], output, cts, res);
636  free(output);
637  }
638 
639  /* Check for clones, disable the clones and reduce the mode to the
640  * lowest common mode */
641  TAILQ_FOREACH(output, &outputs, outputs) {
642  if (!output->active || output->to_be_disabled)
643  continue;
644  DLOG("output %p / %s, position (%d, %d), checking for clones\n",
645  output, output->name, output->rect.x, output->rect.y);
646 
647  for (other = output;
648  other != TAILQ_END(&outputs);
649  other = TAILQ_NEXT(other, outputs)) {
650  if (other == output || !other->active || other->to_be_disabled)
651  continue;
652 
653  if (other->rect.x != output->rect.x ||
654  other->rect.y != output->rect.y)
655  continue;
656 
657  DLOG("output %p has the same position, his mode = %d x %d\n",
658  other, other->rect.width, other->rect.height);
659  uint32_t width = min(other->rect.width, output->rect.width);
660  uint32_t height = min(other->rect.height, output->rect.height);
661 
662  if (update_if_necessary(&(output->rect.width), width) |
663  update_if_necessary(&(output->rect.height), height))
664  output->changed = true;
665 
666  update_if_necessary(&(other->rect.width), width);
667  update_if_necessary(&(other->rect.height), height);
668 
669  DLOG("disabling output %p (%s)\n", other, other->name);
670  other->to_be_disabled = true;
671 
672  DLOG("new output mode %d x %d, other mode %d x %d\n",
673  output->rect.width, output->rect.height,
674  other->rect.width, other->rect.height);
675  }
676  }
677 
678  /* Ensure that all outputs which are active also have a con. This is
679  * necessary because in the next step, a clone might get disabled. Example:
680  * LVDS1 active, VGA1 gets activated as a clone of LVDS1 (has no con).
681  * LVDS1 gets disabled. */
682  TAILQ_FOREACH(output, &outputs, outputs) {
683  if (output->active && output->con == NULL) {
684  DLOG("Need to initialize a Con for output %s\n", output->name);
685  output_init_con(output);
686  output->changed = false;
687  }
688  }
689 
690  /* Handle outputs which have a new mode or are disabled now (either
691  * because the user disabled them or because they are clones) */
692  TAILQ_FOREACH(output, &outputs, outputs) {
693  if (output->to_be_disabled) {
694  output->active = false;
695  DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
696 
697  first = get_first_output();
698 
699  /* TODO: refactor the following code into a nice function. maybe
700  * use an on_destroy callback which is implement differently for
701  * different container types (CT_CONTENT vs. CT_DOCKAREA)? */
702  Con *first_content = output_get_content(first->con);
703 
704  if (output->con != NULL) {
705  /* We need to move the workspaces from the disappearing output to the first output */
706  /* 1: Get the con to focus next, if the disappearing ws is focused */
707  Con *next = NULL;
708  if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
709  DLOG("This output (%p) was focused! Getting next\n", output->con);
710  next = focused;
711  DLOG("next = %p\n", next);
712  }
713 
714  /* 2: iterate through workspaces and re-assign them, fixing the coordinates
715  * of floating containers as we go */
716  Con *current;
717  Con *old_content = output_get_content(output->con);
718  while (!TAILQ_EMPTY(&(old_content->nodes_head))) {
719  current = TAILQ_FIRST(&(old_content->nodes_head));
720  if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
721  /* the workspace is empty and not focused, get rid of it */
722  DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
723  tree_close(current, DONT_KILL_WINDOW, false, false);
724  continue;
725  }
726  DLOG("Detaching current = %p / %s\n", current, current->name);
727  con_detach(current);
728  DLOG("Re-attaching current = %p / %s\n", current, current->name);
729  con_attach(current, first_content, false);
730  DLOG("Fixing the coordinates of floating containers\n");
731  Con *floating_con;
732  TAILQ_FOREACH(floating_con, &(current->floating_head), floating_windows)
733  floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect));
734  DLOG("Done, next\n");
735  }
736  DLOG("re-attached all workspaces\n");
737 
738  if (next) {
739  DLOG("now focusing next = %p\n", next);
740  con_focus(next);
742  }
743 
744  /* 3: move the dock clients to the first output */
745  Con *child;
746  TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) {
747  if (child->type != CT_DOCKAREA)
748  continue;
749  DLOG("Handling dock con %p\n", child);
750  Con *dock;
751  while (!TAILQ_EMPTY(&(child->nodes_head))) {
752  dock = TAILQ_FIRST(&(child->nodes_head));
753  Con *nc;
754  Match *match;
755  nc = con_for_window(first->con, dock->window, &match);
756  DLOG("Moving dock client %p to nc %p\n", dock, nc);
757  con_detach(dock);
758  DLOG("Re-attaching\n");
759  con_attach(dock, nc, false);
760  DLOG("Done\n");
761  }
762  }
763 
764  DLOG("destroying disappearing con %p\n", output->con);
765  tree_close(output->con, DONT_KILL_WINDOW, true, false);
766  DLOG("Done. Should be fine now\n");
767  output->con = NULL;
768  }
769 
770  output->to_be_disabled = false;
771  output->changed = false;
772  }
773 
774  if (output->changed) {
775  output_change_mode(conn, output);
776  output->changed = false;
777  }
778  }
779 
780  if (TAILQ_EMPTY(&outputs)) {
781  ELOG("No outputs found via RandR, disabling\n");
783  }
784 
785  /* Verifies that there is at least one active output as a side-effect. */
787 
788  /* Just go through each active output and assign one workspace */
789  TAILQ_FOREACH(output, &outputs, outputs) {
790  if (!output->active)
791  continue;
792  Con *content = output_get_content(output->con);
793  if (!TAILQ_EMPTY(&(content->nodes_head)))
794  continue;
795  DLOG("Should add ws for output %s\n", output->name);
796  init_ws_for_output(output, content);
797  }
798 
799  /* Focus the primary screen, if possible */
800  TAILQ_FOREACH(output, &outputs, outputs) {
801  if (!output->primary || !output->con)
802  continue;
803 
804  DLOG("Focusing primary output %s\n", output->name);
806  }
807 
808  /* render_layout flushes */
809  tree_render();
810 
811  FREE(res);
812  FREE(primary);
813 }
814 
815 /*
816  * We have just established a connection to the X server and need the initial
817  * XRandR information to setup workspaces for each screen.
818  *
819  */
820 void randr_init(int *event_base) {
821  const xcb_query_extension_reply_t *extreply;
822 
823  extreply = xcb_get_extension_data(conn, &xcb_randr_id);
824  if (!extreply->present) {
826  return;
827  } else randr_query_outputs();
828 
829  if (event_base != NULL)
830  *event_base = extreply->first_event;
831 
832  xcb_randr_select_input(conn, root,
833  XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
834  XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
835  XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
836  XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
837 
838  xcb_flush(conn);
839 }