00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include <time.h>
00016
00017 #include <xcb/randr.h>
00018
00019 #include "all.h"
00020
00021
00022
00023 typedef xcb_randr_get_crtc_info_reply_t crtc_info;
00024 typedef xcb_randr_mode_info_t mode_info;
00025 typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
00026
00027
00028 xcb_randr_get_output_primary_reply_t *primary;
00029
00030
00031 struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
00032
00033 static bool randr_disabled = false;
00034
00035
00036
00037
00038
00039
00040
00041 static Output *get_output_by_id(xcb_randr_output_t id) {
00042 Output *output;
00043 TAILQ_FOREACH(output, &outputs, outputs)
00044 if (output->id == id)
00045 return output;
00046
00047 return NULL;
00048 }
00049
00050
00051
00052
00053
00054 Output *get_output_by_name(const char *name) {
00055 Output *output;
00056 TAILQ_FOREACH(output, &outputs, outputs)
00057 if (output->active &&
00058 strcasecmp(output->name, name) == 0)
00059 return output;
00060
00061 return NULL;
00062 }
00063
00064
00065
00066
00067
00068 Output *get_first_output() {
00069 Output *output;
00070
00071 TAILQ_FOREACH(output, &outputs, outputs)
00072 if (output->active)
00073 return output;
00074
00075 return NULL;
00076 }
00077
00078
00079
00080
00081
00082
00083 Output *get_output_containing(int x, int y) {
00084 Output *output;
00085 TAILQ_FOREACH(output, &outputs, outputs) {
00086 if (!output->active)
00087 continue;
00088 DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
00089 x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
00090 if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
00091 y >= output->rect.y && y < (output->rect.y + output->rect.height))
00092 return output;
00093 }
00094
00095 return NULL;
00096 }
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106 Output *get_output_most(direction_t direction, Output *current) {
00107 Output *output, *candidate = NULL;
00108 int position = 0;
00109 TAILQ_FOREACH(output, &outputs, outputs) {
00110 if (!output->active)
00111 continue;
00112
00113
00114 #define WIN(variable, condition) \
00115 if (variable condition) { \
00116 candidate = output; \
00117 position = variable; \
00118 } \
00119 break;
00120
00121 if (((direction == D_UP) || (direction == D_DOWN)) &&
00122 (current->rect.x != output->rect.x))
00123 continue;
00124
00125 if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
00126 (current->rect.y != output->rect.y))
00127 continue;
00128
00129 switch (direction) {
00130 case D_UP:
00131 WIN(output->rect.y, <= position);
00132 case D_DOWN:
00133 WIN(output->rect.y, >= position);
00134 case D_LEFT:
00135 WIN(output->rect.x, <= position);
00136 case D_RIGHT:
00137 WIN(output->rect.x, >= position);
00138 }
00139 }
00140
00141 assert(candidate != NULL);
00142
00143 return candidate;
00144 }
00145
00146
00147
00148
00149
00150
00151 void disable_randr(xcb_connection_t *conn) {
00152 DLOG("RandR extension unusable, disabling.\n");
00153
00154 Output *s = scalloc(sizeof(Output));
00155
00156 s->active = true;
00157 s->rect.x = 0;
00158 s->rect.y = 0;
00159 s->rect.width = root_screen->width_in_pixels;
00160 s->rect.height = root_screen->height_in_pixels;
00161 s->name = "xroot-0";
00162 output_init_con(s);
00163 init_ws_for_output(s, output_get_content(s->con));
00164
00165 TAILQ_INSERT_TAIL(&outputs, s, outputs);
00166
00167 randr_disabled = true;
00168 }
00169
00170
00171
00172
00173
00174
00175 void output_init_con(Output *output) {
00176 Con *con = NULL, *current;
00177 bool reused = false;
00178
00179 DLOG("init_con for output %s\n", output->name);
00180
00181
00182
00183 TAILQ_FOREACH(current, &(croot->nodes_head), nodes) {
00184 if (strcmp(current->name, output->name) != 0)
00185 continue;
00186
00187 con = current;
00188 reused = true;
00189 DLOG("Using existing con %p / %s\n", con, con->name);
00190 break;
00191 }
00192
00193 if (con == NULL) {
00194 con = con_new(croot, NULL);
00195 FREE(con->name);
00196 con->name = sstrdup(output->name);
00197 con->type = CT_OUTPUT;
00198 con->layout = L_OUTPUT;
00199 con_fix_percent(croot);
00200 }
00201 con->rect = output->rect;
00202 output->con = con;
00203
00204 char *name;
00205 asprintf(&name, "[i3 con] output %s", con->name);
00206 x_set_name(con, name);
00207 FREE(name);
00208
00209 if (reused) {
00210 DLOG("Not adding workspace, this was a reused con\n");
00211 return;
00212 }
00213
00214 DLOG("Changing layout, adding top/bottom dockarea\n");
00215 Con *topdock = con_new(NULL, NULL);
00216 topdock->type = CT_DOCKAREA;
00217 topdock->layout = L_DOCKAREA;
00218 topdock->orientation = VERT;
00219
00220 Match *match = scalloc(sizeof(Match));
00221 match_init(match);
00222 match->dock = M_DOCK_TOP;
00223 match->insert_where = M_BELOW;
00224 TAILQ_INSERT_TAIL(&(topdock->swallow_head), match, matches);
00225
00226 FREE(topdock->name);
00227 topdock->name = sstrdup("topdock");
00228
00229 asprintf(&name, "[i3 con] top dockarea %s", con->name);
00230 x_set_name(topdock, name);
00231 FREE(name);
00232 DLOG("attaching\n");
00233 con_attach(topdock, con, false);
00234
00235
00236
00237 DLOG("adding main content container\n");
00238 Con *content = con_new(NULL, NULL);
00239 content->type = CT_CON;
00240 FREE(content->name);
00241 content->name = sstrdup("content");
00242
00243 asprintf(&name, "[i3 con] content %s", con->name);
00244 x_set_name(content, name);
00245 FREE(name);
00246 con_attach(content, con, false);
00247
00248
00249 Con *bottomdock = con_new(NULL, NULL);
00250 bottomdock->type = CT_DOCKAREA;
00251 bottomdock->layout = L_DOCKAREA;
00252 bottomdock->orientation = VERT;
00253
00254 match = scalloc(sizeof(Match));
00255 match_init(match);
00256 match->dock = M_DOCK_BOTTOM;
00257 match->insert_where = M_BELOW;
00258 TAILQ_INSERT_TAIL(&(bottomdock->swallow_head), match, matches);
00259
00260 FREE(bottomdock->name);
00261 bottomdock->name = sstrdup("bottomdock");
00262
00263 asprintf(&name, "[i3 con] bottom dockarea %s", con->name);
00264 x_set_name(bottomdock, name);
00265 FREE(name);
00266 DLOG("attaching\n");
00267 con_attach(bottomdock, con, false);
00268 }
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280 void init_ws_for_output(Output *output, Con *content) {
00281 char *name;
00282
00283
00284 struct Workspace_Assignment *assignment;
00285 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
00286 if (strcmp(assignment->output, output->name) != 0)
00287 continue;
00288
00289
00290 Con *workspace = NULL, *out;
00291 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
00292 GREP_FIRST(workspace, output_get_content(out),
00293 !strcasecmp(child->name, assignment->name));
00294 if (workspace == NULL)
00295 continue;
00296
00297
00298
00299 Con *workspace_out = con_get_output(workspace);
00300 if (workspace_out == output->con) {
00301 LOG("Workspace \"%s\" assigned to output \"%s\", but it is already "
00302 "there. Do you have two assignment directives for the same "
00303 "workspace in your configuration file?\n",
00304 workspace->name, output->name);
00305 continue;
00306 }
00307
00308
00309 LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
00310 workspace->name, workspace_out->name, output->name);
00311
00312
00313
00314
00315 bool visible = workspace_is_visible(workspace);
00316 Con *previous = NULL;
00317 if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
00318 LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
00319 previous->name, workspace_out->name);
00320 workspace_show(previous->name);
00321 }
00322
00323 con_detach(workspace);
00324 con_attach(workspace, content, false);
00325
00326
00327
00328
00329 if (visible && previous == NULL) {
00330 LOG("There is no workspace left on \"%s\", re-initializing\n",
00331 workspace_out->name);
00332 init_ws_for_output(get_output_by_name(workspace_out->name),
00333 output_get_content(workspace_out));
00334 DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
00335 }
00336 }
00337
00338
00339 if (!TAILQ_EMPTY(&(content->nodes_head))) {
00340
00341
00342 Con *visible = NULL;
00343 GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
00344 if (!visible) {
00345 visible = TAILQ_FIRST(&(content->nodes_head));
00346 focused = content;
00347 workspace_show(visible->name);
00348 }
00349 return;
00350 }
00351
00352
00353 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
00354 if (strcmp(assignment->output, output->name) != 0)
00355 continue;
00356
00357 LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
00358 assignment->name, assignment->output);
00359 focused = content;
00360 workspace_show(assignment->name);
00361 return;
00362 }
00363
00364
00365 DLOG("Now adding a workspace\n");
00366
00367
00368 Con *ws = con_new(NULL, NULL);
00369 ws->type = CT_WORKSPACE;
00370
00371
00372 DLOG("Getting next unused workspace\n");
00373 int c = 0;
00374 bool exists = true;
00375 while (exists) {
00376 Con *out, *current, *child;
00377
00378 c++;
00379
00380 FREE(ws->name);
00381 asprintf(&(ws->name), "%d", c);
00382
00383 exists = false;
00384 TAILQ_FOREACH(out, &(croot->nodes_head), nodes) {
00385 TAILQ_FOREACH(current, &(out->nodes_head), nodes) {
00386 if (current->type != CT_CON)
00387 continue;
00388
00389 TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
00390 if (strcasecmp(child->name, ws->name) != 0)
00391 continue;
00392
00393 exists = true;
00394 break;
00395 }
00396 }
00397 }
00398
00399 DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
00400 }
00401 ws->num = c;
00402 con_attach(ws, content, false);
00403
00404 asprintf(&name, "[i3 con] workspace %s", ws->name);
00405 x_set_name(ws, name);
00406 free(name);
00407
00408 ws->fullscreen_mode = CF_OUTPUT;
00409
00410
00411
00412 if (config.default_orientation == NO_ORIENTATION) {
00413 ws->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
00414 DLOG("Auto orientation. Workspace size set to (%d,%d), setting orientation to %d.\n",
00415 output->rect.width, output->rect.height, ws->orientation);
00416 } else {
00417 ws->orientation = config.default_orientation;
00418 }
00419
00420
00421
00422 con_focus(ws);
00423 }
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436 static void output_change_mode(xcb_connection_t *conn, Output *output) {
00437
00438
00439 DLOG("Output mode changed, updating rect\n");
00440 assert(output->con != NULL);
00441 output->con->rect = output->rect;
00442
00443 Con *content, *workspace, *child;
00444
00445
00446 content = output_get_content(output->con);
00447
00448
00449
00450
00451 if (config.default_orientation == NO_ORIENTATION) {
00452 TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
00453
00454
00455 if (con_num_children(workspace) > 1)
00456 continue;
00457
00458 workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
00459 DLOG("Setting workspace [%d,%s]'s orientation to %d.\n", workspace->num, workspace->name, workspace->orientation);
00460 if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) {
00461 child->orientation = workspace->orientation;
00462 DLOG("Setting child [%d,%s]'s orientation to %d.\n", child->num, child->name, child->orientation);
00463 }
00464 }
00465 }
00466 }
00467
00468
00469
00470
00471
00472
00473
00474
00475
00476 static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
00477 xcb_randr_get_output_info_reply_t *output,
00478 xcb_timestamp_t cts, resources_reply *res) {
00479
00480 crtc_info *crtc;
00481
00482 Output *new = get_output_by_id(id);
00483 bool existing = (new != NULL);
00484 if (!existing)
00485 new = scalloc(sizeof(Output));
00486 new->id = id;
00487 new->primary = (primary && primary->output == id);
00488 FREE(new->name);
00489 asprintf(&new->name, "%.*s",
00490 xcb_randr_get_output_info_name_length(output),
00491 xcb_randr_get_output_info_name(output));
00492
00493 DLOG("found output with name %s\n", new->name);
00494
00495
00496
00497
00498 if (output->crtc == XCB_NONE) {
00499 if (!existing) {
00500 if (new->primary)
00501 TAILQ_INSERT_HEAD(&outputs, new, outputs);
00502 else TAILQ_INSERT_TAIL(&outputs, new, outputs);
00503 } else if (new->active)
00504 new->to_be_disabled = true;
00505 return;
00506 }
00507
00508 xcb_randr_get_crtc_info_cookie_t icookie;
00509 icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
00510 if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
00511 DLOG("Skipping output %s: could not get CRTC (%p)\n",
00512 new->name, crtc);
00513 free(new);
00514 return;
00515 }
00516
00517 bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
00518 update_if_necessary(&(new->rect.y), crtc->y) |
00519 update_if_necessary(&(new->rect.width), crtc->width) |
00520 update_if_necessary(&(new->rect.height), crtc->height);
00521 free(crtc);
00522 new->active = (new->rect.width != 0 && new->rect.height != 0);
00523 if (!new->active) {
00524 DLOG("width/height 0/0, disabling output\n");
00525 return;
00526 }
00527
00528 DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
00529 new->rect.x, new->rect.y);
00530
00531
00532
00533
00534 if (!updated || !existing) {
00535 if (!existing) {
00536 if (new->primary)
00537 TAILQ_INSERT_HEAD(&outputs, new, outputs);
00538 else TAILQ_INSERT_TAIL(&outputs, new, outputs);
00539 }
00540 return;
00541 }
00542
00543 new->changed = true;
00544 }
00545
00546
00547
00548
00549
00550 void randr_query_outputs() {
00551 Output *output, *other, *first;
00552 xcb_randr_get_output_primary_cookie_t pcookie;
00553 xcb_randr_get_screen_resources_current_cookie_t rcookie;
00554 resources_reply *res;
00555
00556
00557
00558 xcb_timestamp_t cts;
00559
00560
00561 xcb_randr_output_t *randr_outputs;
00562
00563 if (randr_disabled)
00564 return;
00565
00566
00567 rcookie = xcb_randr_get_screen_resources_current(conn, root);
00568 pcookie = xcb_randr_get_output_primary(conn, root);
00569
00570 if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
00571 ELOG("Could not get RandR primary output\n");
00572 else DLOG("primary output is %08x\n", primary->output);
00573 if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
00574 disable_randr(conn);
00575 return;
00576 }
00577 cts = res->config_timestamp;
00578
00579 int len = xcb_randr_get_screen_resources_current_outputs_length(res);
00580 randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
00581
00582
00583 xcb_randr_get_output_info_cookie_t ocookie[len];
00584 for (int i = 0; i < len; i++)
00585 ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
00586
00587
00588 for (int i = 0; i < len; i++) {
00589 xcb_randr_get_output_info_reply_t *output;
00590
00591 if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
00592 continue;
00593
00594 handle_output(conn, randr_outputs[i], output, cts, res);
00595 free(output);
00596 }
00597
00598
00599
00600 TAILQ_FOREACH(output, &outputs, outputs) {
00601 if (!output->active || output->to_be_disabled)
00602 continue;
00603 DLOG("output %p / %s, position (%d, %d), checking for clones\n",
00604 output, output->name, output->rect.x, output->rect.y);
00605
00606 for (other = output;
00607 other != TAILQ_END(&outputs);
00608 other = TAILQ_NEXT(other, outputs)) {
00609 if (other == output || !other->active || other->to_be_disabled)
00610 continue;
00611
00612 if (other->rect.x != output->rect.x ||
00613 other->rect.y != output->rect.y)
00614 continue;
00615
00616 DLOG("output %p has the same position, his mode = %d x %d\n",
00617 other, other->rect.width, other->rect.height);
00618 uint32_t width = min(other->rect.width, output->rect.width);
00619 uint32_t height = min(other->rect.height, output->rect.height);
00620
00621 if (update_if_necessary(&(output->rect.width), width) |
00622 update_if_necessary(&(output->rect.height), height))
00623 output->changed = true;
00624
00625 update_if_necessary(&(other->rect.width), width);
00626 update_if_necessary(&(other->rect.height), height);
00627
00628 DLOG("disabling output %p (%s)\n", other, other->name);
00629 other->to_be_disabled = true;
00630
00631 DLOG("new output mode %d x %d, other mode %d x %d\n",
00632 output->rect.width, output->rect.height,
00633 other->rect.width, other->rect.height);
00634 }
00635 }
00636
00637
00638
00639
00640
00641 TAILQ_FOREACH(output, &outputs, outputs) {
00642 if (output->active && output->con == NULL) {
00643 DLOG("Need to initialize a Con for output %s\n", output->name);
00644 output_init_con(output);
00645 output->changed = false;
00646 }
00647 }
00648
00649
00650
00651 TAILQ_FOREACH(output, &outputs, outputs) {
00652 if (output->to_be_disabled) {
00653 output->active = false;
00654 DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
00655
00656 if ((first = get_first_output()) == NULL)
00657 die("No usable outputs available\n");
00658
00659
00660
00661
00662 Con *first_content = output_get_content(first->con);
00663
00664 if (output->con != NULL) {
00665
00666
00667 Con *next = NULL;
00668 if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
00669 DLOG("This output (%p) was focused! Getting next\n", output->con);
00670 next = con_next_focused(output->con);
00671 DLOG("next = %p\n", next);
00672 }
00673
00674
00675 Con *current;
00676 Con *old_content = output_get_content(output->con);
00677 while (!TAILQ_EMPTY(&(old_content->nodes_head))) {
00678 current = TAILQ_FIRST(&(old_content->nodes_head));
00679 DLOG("Detaching current = %p / %s\n", current, current->name);
00680 con_detach(current);
00681 DLOG("Re-attaching current = %p / %s\n", current, current->name);
00682 con_attach(current, first_content, false);
00683 DLOG("Done, next\n");
00684 }
00685 DLOG("re-attached all workspaces\n");
00686
00687 if (next) {
00688 DLOG("now focusing next = %p\n", next);
00689 con_focus(next);
00690 }
00691
00692
00693 Con *child;
00694 TAILQ_FOREACH(child, &(output->con->nodes_head), nodes) {
00695 if (child->type != CT_DOCKAREA)
00696 continue;
00697 DLOG("Handling dock con %p\n", child);
00698 Con *dock;
00699 while (!TAILQ_EMPTY(&(child->nodes_head))) {
00700 dock = TAILQ_FIRST(&(child->nodes_head));
00701 Con *nc;
00702 Match *match;
00703 nc = con_for_window(first->con, dock->window, &match);
00704 DLOG("Moving dock client %p to nc %p\n", dock, nc);
00705 con_detach(dock);
00706 DLOG("Re-attaching\n");
00707 con_attach(dock, nc, false);
00708 DLOG("Done\n");
00709 }
00710 }
00711
00712 DLOG("destroying disappearing con %p\n", output->con);
00713 tree_close(output->con, DONT_KILL_WINDOW, true);
00714 DLOG("Done. Should be fine now\n");
00715 output->con = NULL;
00716 }
00717
00718 output->to_be_disabled = false;
00719 output->changed = false;
00720 }
00721
00722 if (output->changed) {
00723 output_change_mode(conn, output);
00724 output->changed = false;
00725 }
00726 }
00727
00728 if (TAILQ_EMPTY(&outputs)) {
00729 ELOG("No outputs found via RandR, disabling\n");
00730 disable_randr(conn);
00731 }
00732
00733 ewmh_update_workarea();
00734
00735
00736 TAILQ_FOREACH(output, &outputs, outputs) {
00737 if (!output->active)
00738 continue;
00739 Con *content = output_get_content(output->con);
00740 if (!TAILQ_EMPTY(&(content->nodes_head)))
00741 continue;
00742 DLOG("Should add ws for output %s\n", output->name);
00743 init_ws_for_output(output, content);
00744 }
00745
00746
00747 TAILQ_FOREACH(output, &outputs, outputs) {
00748 if (!output->primary || !output->con)
00749 continue;
00750
00751 DLOG("Focusing primary output %s\n", output->name);
00752 con_focus(con_descend_focused(output->con));
00753 }
00754
00755
00756 tree_render();
00757
00758 FREE(res);
00759 FREE(primary);
00760 }
00761
00762
00763
00764
00765
00766
00767 void randr_init(int *event_base) {
00768 const xcb_query_extension_reply_t *extreply;
00769
00770 extreply = xcb_get_extension_data(conn, &xcb_randr_id);
00771 if (!extreply->present)
00772 disable_randr(conn);
00773 else randr_query_outputs();
00774
00775 if (event_base != NULL)
00776 *event_base = extreply->first_event;
00777
00778 xcb_randr_select_input(conn, root,
00779 XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
00780 XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
00781 XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
00782 XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
00783
00784 xcb_flush(conn);
00785 }