]> git.sur5r.net Git - i3/i3/blobdiff - src/randr.c
x.c: correctly free con->frame_buffer in _x_con_kill
[i3/i3] / src / randr.c
index df63826d57ad9fb0ad602ebdfcb523c65d25de7c..fb127ab5622f1ada69da11b552a8778735972ec9 100644 (file)
@@ -5,7 +5,7 @@
  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * For more information on RandR, please see the X.org RandR specification at
- * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
+ * https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
  * (take your time to read it completely, it answers all questions).
  *
  */
@@ -48,10 +48,18 @@ Output *get_output_by_name(const char *name, const bool require_active) {
     Output *output;
     bool get_primary = (strcasecmp("primary", name) == 0);
     TAILQ_FOREACH(output, &outputs, outputs) {
-        if ((output->primary && get_primary) ||
-            ((!require_active || output->active) && strcasecmp(output_primary_name(output), name) == 0)) {
+        if (output->primary && get_primary) {
             return output;
         }
+        if (require_active && !output->active) {
+            continue;
+        }
+        struct output_name *output_name;
+        SLIST_FOREACH(output_name, &output->names_head, names) {
+            if (strcasecmp(output_name->name, name) == 0) {
+                return output;
+            }
+        }
     }
 
     return NULL;
@@ -106,6 +114,20 @@ Output *get_output_containing(unsigned int x, unsigned int y) {
     return NULL;
 }
 
+/*
+ * Returns the active output which contains the midpoint of the given rect. If
+ * such an output doesn't exist, returns the output which contains most of the
+ * rectangle or NULL if there is no output which intersects with it.
+ *
+ */
+Output *get_output_from_rect(Rect rect) {
+    unsigned int mid_x = rect.x + rect.width / 2;
+    unsigned int mid_y = rect.y + rect.height / 2;
+    Output *output = get_output_containing(mid_x, mid_y);
+
+    return output ? output : output_containing_rect(rect);
+}
+
 /*
  * Returns the active output which spans exactly the area specified by
  * rect or NULL if there is no output like this.
@@ -128,27 +150,37 @@ Output *get_output_with_dimensions(Rect rect) {
 }
 
 /*
- * In contained_by_output, we check if any active output contains part of the container.
+ * In output_containing_rect, we check if any active output contains part of the container.
  * We do this by checking if the output rect is intersected by the Rect.
  * This is the 2-dimensional counterpart of get_output_containing.
- * Since we don't actually need the outputs intersected by the given Rect (There could
- * be many), we just return true or false for convenience.
+ * Returns the output with the maximum intersecting area.
  *
  */
-bool contained_by_output(Rect rect) {
+Output *output_containing_rect(Rect rect) {
     Output *output;
     int lx = rect.x, uy = rect.y;
     int rx = rect.x + rect.width, by = rect.y + rect.height;
+    long max_area = 0;
+    Output *result = NULL;
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
             continue;
+        int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y;
+        int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height);
         DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
              rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
-        if (rx >= (int)output->rect.x && lx <= (int)(output->rect.x + output->rect.width) &&
-            by >= (int)output->rect.y && uy <= (int)(output->rect.y + output->rect.height))
-            return true;
+        int left = max(lx, lx_o);
+        int right = min(rx, rx_o);
+        int bottom = min(by, by_o);
+        int top = max(uy, uy_o);
+        if (left < right && bottom > top) {
+            long area = (right - left) * (bottom - top);
+            if (area > max_area) {
+                result = output;
+            }
+        }
     }
-    return false;
+    return result;
 }
 
 /*
@@ -372,6 +404,10 @@ void output_init_con(Output *output) {
     FREE(name);
     DLOG("attaching\n");
     con_attach(bottomdock, con, false);
+
+    /* Change focus to the content container */
+    TAILQ_REMOVE(&(con->focus_head), content, focused);
+    TAILQ_INSERT_HEAD(&(con->focus_head), content, focused);
 }
 
 /*
@@ -384,18 +420,17 @@ void output_init_con(Output *output) {
  * • Create the first unused workspace.
  *
  */
-void init_ws_for_output(Output *output, Con *content) {
+void init_ws_for_output(Output *output) {
+    Con *content = output_get_content(output->con);
+    Con *previous_focus = con_get_workspace(focused);
+
     /* go through all assignments and move the existing workspaces to this output */
     struct Workspace_Assignment *assignment;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
-
-        /* check if this workspace actually exists */
-        Con *workspace = NULL, *out;
-        TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
-        GREP_FIRST(workspace, output_get_content(out),
-                   !strcasecmp(child->name, assignment->name));
+        }
+        Con *workspace = get_existing_workspace_by_name(assignment->name);
         if (workspace == NULL)
             continue;
 
@@ -410,49 +445,18 @@ void init_ws_for_output(Output *output, Con *content) {
             continue;
         }
 
-        /* if so, move it over */
-        LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
-            workspace->name, workspace_out->name, output_primary_name(output));
-
-        /* if the workspace is currently visible on that output, we need to
-         * switch to a different workspace - otherwise the output would end up
-         * with no active workspace */
-        bool visible = workspace_is_visible(workspace);
-        Con *previous = NULL;
-        if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
-            LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
-                previous->name, workspace_out->name);
-            workspace_show(previous);
-        }
-
-        /* Render the output on which the workspace was to get correct Rects.
-         * Then, we need to work with the "content" container, since we cannot
-         * be sure that the workspace itself was rendered at all (in case it’s
-         * invisible, it won’t be rendered). */
-        render_con(workspace_out, false);
-        Con *ws_out_content = output_get_content(workspace_out);
-
-        Con *floating_con;
-        TAILQ_FOREACH(floating_con, &(workspace->floating_head), floating_windows)
-        /* NB: We use output->con here because content is not yet rendered,
-             * so it has a rect of {0, 0, 0, 0}. */
-        floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect));
-
-        con_detach(workspace);
-        con_attach(workspace, content, false);
-
-        /* In case the workspace we just moved was visible but there was no
-         * other workspace to switch to, we need to initialize the source
-         * output as well */
-        if (visible && previous == NULL) {
-            LOG("There is no workspace left on \"%s\", re-initializing\n",
-                workspace_out->name);
-            init_ws_for_output(get_output_by_name(workspace_out->name, true),
-                               output_get_content(workspace_out));
-            DLOG("Done re-initializing, continuing with \"%s\"\n", output_primary_name(output));
-        }
+        DLOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
+             workspace->name, workspace_out->name, output_primary_name(output));
+        /* Need to copy output's rect since content is not yet rendered. We
+         * can't call render_con here because render_output only proceeds if a
+         * workspace exists. */
+        content->rect = output->con->rect;
+        workspace_move_to_output(workspace, output);
     }
 
+    /* Temporarily set the focused container, might not be initialized yet. */
+    focused = content;
+
     /* if a workspace exists, we are done now */
     if (!TAILQ_EMPTY(&(content->nodes_head))) {
         /* ensure that one of the workspaces is actually visible (in fullscreen
@@ -461,30 +465,31 @@ void init_ws_for_output(Output *output, Con *content) {
         GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
         if (!visible) {
             visible = TAILQ_FIRST(&(content->nodes_head));
-            focused = content;
             workspace_show(visible);
         }
-        return;
+        goto restore_focus;
     }
 
     /* otherwise, we create the first assigned ws for this output */
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
-        if (strcmp(assignment->output, output_primary_name(output)) != 0)
+        if (!output_triggers_assignment(output, assignment)) {
             continue;
+        }
 
         LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
             assignment->name, assignment->output);
-        focused = content;
         workspace_show_by_name(assignment->name);
-        return;
+        goto restore_focus;
     }
 
     /* if there is still no workspace, we create the first free workspace */
     DLOG("Now adding a workspace\n");
-    Con *ws = create_workspace_on_output(output, content);
+    workspace_show(create_workspace_on_output(output, content));
 
-    /* TODO: Set focus in main.c */
-    con_focus(ws);
+restore_focus:
+    if (previous_focus) {
+        workspace_show(previous_focus);
+    }
 }
 
 /*
@@ -517,7 +522,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
     }
 
     /* If default_orientation is NO_ORIENTATION, we change the orientation of
-     * the workspaces and their childs depending on output resolution. This is
+     * the workspaces and their children depending on output resolution. This is
      * only done for workspaces with maximum one child. */
     if (config.default_orientation == NO_ORIENTATION) {
         TAILQ_FOREACH(workspace, &(content->nodes_head), nodes) {
@@ -599,9 +604,39 @@ static bool randr_query_outputs_15(void) {
         if (new == NULL) {
             new = scalloc(1, sizeof(Output));
 
+            SLIST_INIT(&new->names_head);
+
+            /* Register associated output names in addition to the monitor name */
+            xcb_randr_output_t *randr_outputs = xcb_randr_monitor_info_outputs(monitor_info);
+            int randr_output_len = xcb_randr_monitor_info_outputs_length(monitor_info);
+            for (int i = 0; i < randr_output_len; i++) {
+                xcb_randr_output_t randr_output = randr_outputs[i];
+
+                xcb_randr_get_output_info_reply_t *info =
+                    xcb_randr_get_output_info_reply(conn,
+                                                    xcb_randr_get_output_info(conn, randr_output, monitors->timestamp),
+                                                    NULL);
+
+                if (info != NULL && info->crtc != XCB_NONE) {
+                    char *oname;
+                    sasprintf(&oname, "%.*s",
+                              xcb_randr_get_output_info_name_length(info),
+                              xcb_randr_get_output_info_name(info));
+
+                    if (strcmp(name, oname) != 0) {
+                        struct output_name *output_name = scalloc(1, sizeof(struct output_name));
+                        output_name->name = sstrdup(oname);
+                        SLIST_INSERT_HEAD(&new->names_head, output_name, names);
+                    } else {
+                        free(oname);
+                    }
+                }
+                FREE(info);
+            }
+
+            /* Insert the monitor name last, so that it's used as the primary name */
             struct output_name *output_name = scalloc(1, sizeof(struct output_name));
             output_name->name = sstrdup(name);
-            SLIST_INIT(&new->names_head);
             SLIST_INSERT_HEAD(&new->names_head, output_name, names);
 
             if (monitor_info->primary) {
@@ -794,8 +829,9 @@ void randr_query_outputs(void) {
     /* If there's no randr output, enable the output covering the root window. */
     if (any_randr_output_active()) {
         DLOG("Active RandR output found. Disabling root output.\n");
-        if (root_output->active)
+        if (root_output && root_output->active) {
             root_output->to_be_disabled = true;
+        }
     } else {
         DLOG("No active RandR output found. Enabling root output.\n");
         root_output->active = true;
@@ -873,7 +909,7 @@ void randr_query_outputs(void) {
         if (!TAILQ_EMPTY(&(content->nodes_head)))
             continue;
         DLOG("Should add ws for output %s\n", output_primary_name(output));
-        init_ws_for_output(output, content);
+        init_ws_for_output(output);
     }
 
     /* Focus the primary screen, if possible */
@@ -882,7 +918,9 @@ void randr_query_outputs(void) {
             continue;
 
         DLOG("Focusing primary output %s\n", output_primary_name(output));
-        con_focus(con_descend_focused(output->con));
+        Con *content = output_get_content(output->con);
+        Con *ws = TAILQ_FIRST(&(content)->focus_head);
+        workspace_show(ws);
     }
 
     /* render_layout flushes */
@@ -910,13 +948,8 @@ void randr_disable_output(Output *output) {
 
     if (output->con != NULL) {
         /* We need to move the workspaces from the disappearing output to the first output */
-        /* 1: Get the con to focus next, if the disappearing ws is focused */
-        Con *next = NULL;
-        if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
-            DLOG("This output (%p) was focused! Getting next\n", output->con);
-            next = focused;
-            DLOG("next = %p\n", next);
-        }
+        /* 1: Get the con to focus next */
+        Con *next = focused;
 
         /* 2: iterate through workspaces and re-assign them, fixing the coordinates
          * of floating containers as we go */
@@ -927,7 +960,7 @@ void randr_disable_output(Output *output) {
             if (current != next && TAILQ_EMPTY(&(current->focus_head))) {
                 /* the workspace is empty and not focused, get rid of it */
                 DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name);
-                tree_close_internal(current, DONT_KILL_WINDOW, false, false);
+                tree_close_internal(current, DONT_KILL_WINDOW, false);
                 continue;
             }
             DLOG("Detaching current = %p / %s\n", current, current->name);
@@ -939,10 +972,9 @@ void randr_disable_output(Output *output) {
             TAILQ_FOREACH(floating_con, &(current->floating_head), floating_windows) {
                 floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect));
             }
-            DLOG("Done, next\n");
         }
-        DLOG("re-attached all workspaces\n");
 
+        /* Restore focus after con_detach / con_attach. next can be NULL, see #3523. */
         if (next) {
             DLOG("now focusing next = %p\n", next);
             con_focus(next);
@@ -973,7 +1005,7 @@ void randr_disable_output(Output *output) {
         Con *con = output->con;
         /* clear the pointer before calling tree_close_internal in which the memory is freed */
         output->con = NULL;
-        tree_close_internal(con, DONT_KILL_WINDOW, true, false);
+        tree_close_internal(con, DONT_KILL_WINDOW, true);
         DLOG("Done. Should be fine now\n");
     }
 
@@ -984,7 +1016,7 @@ void randr_disable_output(Output *output) {
 static void fallback_to_root_output(void) {
     root_output->active = true;
     output_init_con(root_output);
-    init_ws_for_output(root_output, output_get_content(root_output->con));
+    init_ws_for_output(root_output);
 }
 
 /*
@@ -1010,8 +1042,8 @@ void randr_init(int *event_base, const bool disable_randr15) {
         xcb_randr_query_version_reply(
             conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
     if (err != NULL) {
-        free(err);
         ELOG("Could not query RandR version: X11 error code %d\n", err->error_code);
+        free(err);
         fallback_to_root_output();
         return;
     }