]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #3192 from Exagone313/next
authorOrestis <orestisf1993@gmail.com>
Thu, 29 Mar 2018 09:16:12 +0000 (12:16 +0300)
committerGitHub <noreply@github.com>
Thu, 29 Mar 2018 09:16:12 +0000 (12:16 +0300)
translate_keysyms: fix potential memory leak

26 files changed:
docs/userguide
include/atoms_NET_SUPPORTED.xmacro
include/commands.h
include/ewmh.h
include/floating.h
include/randr.h
parser-specs/commands.spec
src/commands.c
src/con.c
src/config.c
src/config_directives.c
src/ewmh.c
src/floating.c
src/move.c
src/randr.c
src/startup.c
src/tree.c
src/workspace.c
src/x.c
testcases/lib/i3test.pm.in
testcases/t/158-wm_take_focus.t
testcases/t/253-multiple-net-wm-state-atoms.t
testcases/t/294-focus-order.t
testcases/t/295-net-wm-state-focused.t [new file with mode: 0644]
testcases/t/296-regress-focus-behind-fullscreen-floating.t [new file with mode: 0644]
testcases/t/516-move.t

index 9ba181e03c6020efa0c19e0e24c98dd924b82234..5fc36585d02b3fb7a67bfe7adc43e6aca6bf2a62 100644 (file)
@@ -2030,10 +2030,13 @@ Use the +move+ command to move a container.
 # defaults to 10 pixels.
 move <left|right|down|up> [<px> px]
 
-# Moves the container either to a specific location
-# or to the center of the screen. If 'absolute' is
-# used, it is moved to the center of all outputs.
-move [absolute] position <pos_x> [px] <pos_y> [px]
+# Moves the container to the specified pos_x and pos_y
+# coordinates on the screen.
+move position <pos_x> [px] <pos_y> [px]
+
+# Moves the container to the center of the screen.
+# If 'absolute' is used, it is moved to the center of
+# all outputs.
 move [absolute] position center
 
 # Moves the container to the current position of the
index a7b9676d9817e21dcd142c6f62fc9606cada2773..a81948a983fde44853e7da19889743a4adb78f96 100644 (file)
@@ -8,6 +8,7 @@ xmacro(_NET_WM_STATE_FULLSCREEN)
 xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
 xmacro(_NET_WM_STATE_MODAL)
 xmacro(_NET_WM_STATE_HIDDEN)
+xmacro(_NET_WM_STATE_FOCUSED)
 xmacro(_NET_WM_STATE)
 xmacro(_NET_WM_WINDOW_TYPE)
 xmacro(_NET_WM_WINDOW_TYPE_NORMAL)
index 1057f021db5a3d2aa45034b3557f51071cf03c07..aaa0875fbf399380b503545bcadd816d148ac198 100644 (file)
@@ -264,7 +264,7 @@ void cmd_focus_output(I3_CMD, const char *name);
  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
  *
  */
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y);
+void cmd_move_window_to_position(I3_CMD, long x, long y);
 
 /**
  * Implementation of 'move [window|container] [to] [absolute] position center
index 5844faa660a3886f78d0e1669f3a162c1385fbc9..01ae67f969c943392ab8506b583cd3b911d5e019 100644 (file)
@@ -83,6 +83,12 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows);
  */
 void ewmh_update_sticky(xcb_window_t window, bool sticky);
 
+/**
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused);
+
 /**
  * Set up the EWMH hints on the root window.
  *
index babfafc9a583f3321dbe32c4394b9d4ca8e22070..4382437bbbb8f01ef262ca99b4db0fb76ddc2dce 100644 (file)
@@ -143,7 +143,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
  * outputs.
  *
  */
-void floating_reposition(Con *con, Rect newrect);
+bool floating_reposition(Con *con, Rect newrect);
 
 /**
  * Sets size of the CT_FLOATING_CON to specified dimensions. Might limit the
index bfbfd5a90bcbabcec5b261ad39e8c51d6686cb0f..39182c54b3e76d0851cebeb7d3ed99f68e4109b3 100644 (file)
@@ -88,6 +88,14 @@ Output *get_output_by_name(const char *name, const bool require_active);
  */
 Output *get_output_containing(unsigned int x, unsigned int y);
 
+/**
+ * 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);
+
 /**
  * Returns the active output which spans exactly the area specified by
  * rect or NULL if there is no output like this.
@@ -95,15 +103,14 @@ Output *get_output_containing(unsigned int x, unsigned int y);
  */
 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);
 
 /**
  * Gets the output which is the next one in the given direction.
index 0289fa1ab6018a404cb2445f5fa57b61eaf283eb..4048768e5cee375b1ef31e55ca0ceef50e0c0fcd 100644 (file)
@@ -396,7 +396,7 @@ state MOVE_TO_POSITION_X:
 
 state MOVE_TO_POSITION_Y:
   'px', end
-      -> call cmd_move_window_to_position($method, &coord_x, &coord_y)
+      -> call cmd_move_window_to_position(&coord_x, &coord_y)
 
 # mode <string>
 state MODE:
index 899bbb90c8f0854254b4d36b52e0e2a21619e034..500a697eed194aec622d9b82e9f1394edd2169bf 100644 (file)
@@ -789,11 +789,10 @@ void cmd_nop(I3_CMD, const char *comment) {
  *
  */
 void cmd_append_layout(I3_CMD, const char *cpath) {
-    char *path = sstrdup(cpath);
-    LOG("Appending layout \"%s\"\n", path);
+    LOG("Appending layout \"%s\"\n", cpath);
 
     /* Make sure we allow paths like '~/.i3/layout.json' */
-    path = resolve_tilde(path);
+    char *path = resolve_tilde(cpath);
 
     char *buf = NULL;
     ssize_t len;
@@ -1694,7 +1693,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
  *
  */
-void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
+void cmd_move_window_to_position(I3_CMD, long x, long y) {
     bool has_error = false;
 
     owindow *current;
@@ -1712,27 +1711,18 @@ void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
             continue;
         }
 
-        if (strcmp(method, "absolute") == 0) {
-            current->con->parent->rect.x = x;
-            current->con->parent->rect.y = y;
+        Rect newrect = current->con->parent->rect;
 
-            DLOG("moving to absolute position %ld %ld\n", x, y);
-            floating_maybe_reassign_ws(current->con->parent);
-            cmd_output->needs_tree_render = true;
-        }
+        DLOG("moving to position %ld %ld\n", x, y);
+        newrect.x = x;
+        newrect.y = y;
 
-        if (strcmp(method, "position") == 0) {
-            Rect newrect = current->con->parent->rect;
-
-            DLOG("moving to position %ld %ld\n", x, y);
-            newrect.x = x;
-            newrect.y = y;
-
-            floating_reposition(current->con->parent, newrect);
+        if (!floating_reposition(current->con->parent, newrect)) {
+            yerror("Cannot move window/container out of bounds.");
+            has_error = true;
         }
     }
 
-    // XXX: default reply for now, make this a better reply
     if (!has_error)
         ysuccess(true);
 }
index 985d07da2dd8e58058fe7e0bea207f62ab7ac572..3861f04678df769b74cbc807ff3ce3ddc33e3918 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -1140,7 +1140,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
     /* 1: save the container which is going to be focused after the current
      * container is moved away */
-    Con *focus_next = con_next_focused(con);
+    Con *focus_next = NULL;
+    if (!ignore_focus && source_ws == current_ws) {
+        focus_next = con_descend_focused(source_ws);
+        if (focus_next == con || con_has_parent(focus_next, con)) {
+            focus_next = con_next_focused(con);
+        }
+    }
 
     /* 2: we go up one level, but only when target is a normal container */
     if (target->type != CT_WORKSPACE) {
@@ -1170,20 +1176,6 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
             floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect));
         } else
             DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates);
-
-        /* If moving to a visible workspace, call show so it can be considered
-         * focused. Must do before attaching because workspace_show checks to see
-         * if focused container is in its area. */
-        if (!ignore_focus && workspace_is_visible(target_ws)) {
-            workspace_show(target_ws);
-
-            /* Don’t warp if told so (when dragging floating windows with the
-             * mouse for example) */
-            if (dont_warp)
-                x_set_warp_to(NULL);
-            else
-                x_set_warp_to(&(con->rect));
-        }
     }
 
     /* If moving a fullscreen container and the destination already has a
@@ -1222,15 +1214,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
         /* Restore focus if the output's focused workspace has changed. */
         if (con_get_workspace(focused) != old_focus)
-            con_activate(old_focus);
+            con_focus(old_focus);
     }
 
     /* 7: when moving to another workspace, we leave the focus on the current
      * workspace. (see also #809) */
-
-    /* Descend focus stack in case focus_next is a workspace which can
-     * occur if we move to the same workspace.  Also show current workspace
-     * to ensure it is focused. */
     if (!ignore_focus) {
         workspace_show(current_ws);
         if (dont_warp) {
@@ -1241,7 +1229,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
 
     /* Set focus only if con was on current workspace before moving.
      * Otherwise we would give focus to some window on different workspace. */
-    if (!ignore_focus && source_ws == current_ws)
+    if (focus_next)
         con_activate(con_descend_focused(focus_next));
 
     /* 8. If anything within the container is associated with a startup sequence,
@@ -1935,7 +1923,6 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
              * now let's activate the current layout (next in list) */
             if (current_layout_found) {
                 new_layout = layout;
-                free(tm_dup);
                 break;
             }
 
@@ -1943,6 +1930,7 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
                 current_layout_found = true;
             }
         }
+        free(tm_dup);
 
         if (new_layout != L_DEFAULT) {
             con_set_layout(con, new_layout);
index fd379fdea475d234a47932b5f234b9116d144c9a..95b7ec9801f424e91402482589fc70e2d6efacbb 100644 (file)
@@ -99,7 +99,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
         struct Assignment *assign;
         while (!TAILQ_EMPTY(&assignments)) {
             assign = TAILQ_FIRST(&assignments);
-            if (assign->type == A_TO_WORKSPACE)
+            if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER)
                 FREE(assign->dest.workspace);
             else if (assign->type == A_COMMAND)
                 FREE(assign->dest.command);
index da1fb580514278c324f82513f7aa524577d54d5a..491c840afbf71db3b52518f7a5b2eb1962e7c272 100644 (file)
@@ -328,21 +328,18 @@ CFGFUN(workspace, const char *workspace, const char *output) {
      * don’t have assignments of a single workspace to different
      * outputs */
     struct Workspace_Assignment *assignment;
-    bool duplicate = false;
     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
         if (strcasecmp(assignment->name, workspace) == 0) {
             ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
                  workspace);
-            assignment->output = sstrdup(output);
-            duplicate = true;
+            return;
         }
     }
-    if (!duplicate) {
-        assignment = scalloc(1, sizeof(struct Workspace_Assignment));
-        assignment->name = sstrdup(workspace);
-        assignment->output = sstrdup(output);
-        TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
-    }
+
+    assignment = scalloc(1, sizeof(struct Workspace_Assignment));
+    assignment->name = sstrdup(workspace);
+    assignment->output = sstrdup(output);
+    TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
 }
 
 CFGFUN(ipc_socket, const char *path) {
index f8422bdadaf1f103f146ecde24a4a94cd279a30a..e5dcafcb012a6572e336e23594fae26301bd3a95 100644 (file)
@@ -284,6 +284,20 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
     }
 }
 
+/*
+ * Set or remove _NEW_WM_STATE_FOCUSED on the window.
+ *
+ */
+void ewmh_update_focused(xcb_window_t window, bool is_focused) {
+    if (is_focused) {
+        DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+        xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+    } else {
+        DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
+        xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
+    }
+}
+
 /*
  * Set up the EWMH hints on the root window.
  *
index e958153d6a647ab5242188d932f28e1b3ae19e98..1bc4996a17c407868ff4e12d6e3be16463fd5b45 100644 (file)
@@ -284,10 +284,7 @@ void floating_enable(Con *con, bool automatic) {
 
     /* Sanity check: Are the coordinates on the appropriate output? If not, we
      * need to change them */
-    Output *current_output = get_output_containing(nc->rect.x +
-                                                       (nc->rect.width / 2),
-                                                   nc->rect.y + (nc->rect.height / 2));
-
+    Output *current_output = get_output_from_rect(nc->rect);
     Con *correct_output = con_get_output(ws);
     if (!current_output || current_output->con != correct_output) {
         DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n",
@@ -295,11 +292,13 @@ void floating_enable(Con *con, bool automatic) {
 
         /* If moving from one output to another, keep the relative position
          * consistent (e.g. a centered dialog will remain centered). */
-        if (current_output)
+        if (current_output) {
             floating_fix_coordinates(nc, &current_output->con->rect, &correct_output->rect);
-        else {
-            nc->rect.x = correct_output->rect.x;
-            nc->rect.y = correct_output->rect.y;
+            /* Make sure that the result is in the correct output. */
+            current_output = get_output_from_rect(nc->rect);
+        }
+        if (!current_output || current_output->con != correct_output) {
+            floating_center(nc, ws->rect);
         }
     }
 
@@ -320,21 +319,6 @@ void floating_enable(Con *con, bool automatic) {
     if (set_focus)
         con_activate(con);
 
-    /* Check if we need to re-assign it to a different workspace because of its
-     * coordinates and exit if that was done successfully. */
-    if (floating_maybe_reassign_ws(nc)) {
-        goto done;
-    }
-
-    /* Sanitize coordinates: Check if they are on any output */
-    if (get_output_containing(nc->rect.x, nc->rect.y) != NULL) {
-        goto done;
-    }
-
-    ELOG("No output found at destination coordinates, centering floating window on current ws\n");
-    floating_center(nc, ws->rect);
-
-done:
     floating_set_hint_atom(nc, true);
     ipc_send_window_event("floating", con);
 }
@@ -429,9 +413,7 @@ void floating_raise_con(Con *con) {
  *
  */
 bool floating_maybe_reassign_ws(Con *con) {
-    Output *output = get_output_containing(
-        con->rect.x + (con->rect.width / 2),
-        con->rect.y + (con->rect.height / 2));
+    Output *output = get_output_from_rect(con->rect);
 
     if (!output) {
         ELOG("No output found at destination coordinates?\n");
@@ -687,8 +669,7 @@ struct drag_x11_cb {
     const void *extra;
 };
 
-static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) {
     xcb_motion_notify_event_t *last_motion_notify = NULL;
     xcb_generic_event_t *event;
 
@@ -749,12 +730,14 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
 
         if (dragloop->result != DRAGGING) {
             free(last_motion_notify);
-            return;
+            ev_break(EV_A_ EVBREAK_ONE);
+            return true;
         }
     }
 
-    if (last_motion_notify == NULL)
-        return;
+    if (last_motion_notify == NULL) {
+        return true;
+    }
 
     /* Ensure that we are either dragging the resize handle (con is NULL) or that the
      * container still exists. The latter might not be true, e.g., if the window closed
@@ -767,9 +750,17 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
             last_motion_notify->root_y,
             dragloop->extra);
     }
-    free(last_motion_notify);
+    FREE(last_motion_notify);
 
     xcb_flush(conn);
+    return false;
+}
+
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
+    struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
+    while (!drain_drag_events(EV_A, dragloop)) {
+        /* repeatedly drain events: draining might produce additional ones */
+    }
 }
 
 /*
@@ -844,8 +835,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
     main_set_x11_cb(false);
     ev_prepare_start(main_loop, prepare);
 
-    while (loop.result == DRAGGING)
-        ev_run(main_loop, EVRUN_ONCE);
+    ev_loop(main_loop, 0);
 
     ev_prepare_stop(main_loop, prepare);
     main_set_x11_cb(true);
@@ -864,12 +854,12 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
  * outputs.
  *
  */
-void floating_reposition(Con *con, Rect newrect) {
+bool floating_reposition(Con *con, Rect newrect) {
     /* Sanity check: Are the new coordinates on any output? If not, we
      * ignore that request. */
-    if (!contained_by_output(newrect)) {
+    if (!output_containing_rect(newrect)) {
         ELOG("No output found at destination coordinates. Not repositioning.\n");
-        return;
+        return false;
     }
 
     con->rect = newrect;
@@ -881,6 +871,7 @@ void floating_reposition(Con *con, Rect newrect) {
         con->scratchpad_state = SCRATCHPAD_CHANGED;
 
     tree_render();
+    return true;
 }
 
 /*
index a60a27caae947e9c97a92b6135e686aaf807752b..e8620c247b0339d4b0fe529ef97108816ad703c9 100644 (file)
@@ -256,7 +256,13 @@ void tree_move(Con *con, int direction) {
         return;
     }
 
-    if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
+    if (con->fullscreen_mode == CF_GLOBAL) {
+        DLOG("Not moving fullscreen global container\n");
+        return;
+    }
+
+    if ((con->fullscreen_mode == CF_OUTPUT) ||
+        (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1)) {
         /* This is the only con on this workspace */
         move_to_output_directed(con, direction);
         return;
index 85add08fa41ea2cb64a60fc194a3fe65d02a1550..c43c645517c215b99a2bd7d306dfc5a058ad1a87 100644 (file)
@@ -114,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.
@@ -136,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;
 }
 
 /*
index 166842e07c04e8471123ec331012837ffe189220..1e733fcb8670399abf991954d368d7df869f904a 100644 (file)
@@ -49,6 +49,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) {
 
     if (!sequence) {
         DLOG("Sequence already deleted, nevermind.\n");
+        free(w);
         return;
     }
 
index 6c6a614e22d46479381e74447fd12f55dd378bb1..96766b6a4f9346e37682d44f03c45a3e84728441 100644 (file)
@@ -567,9 +567,16 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         if (!workspace)
             return false;
 
-        Con *focus = con_descend_tiling_focused(workspace);
-        if (focus == workspace) {
-            focus = con_descend_focused(workspace);
+        /* Use descend_focused first to give higher priority to floating or
+         * tiling fullscreen containers. */
+        Con *focus = con_descend_focused(workspace);
+        if (focus->fullscreen_mode == CF_NONE) {
+            Con *focus_tiling = con_descend_tiling_focused(workspace);
+            /* If descend_tiling returned a workspace then focus is either a
+             * floating container or the same workspace. */
+            if (focus_tiling != workspace) {
+                focus = focus_tiling;
+            }
         }
 
         workspace_show(workspace);
index 8c46a94917b9f2f3dbc18848e8154d157a99fa06..edd3ee6f0306f69f96041f8e30d76b658aa01bfe 100644 (file)
@@ -340,7 +340,7 @@ static void workspace_reassign_sticky(Con *con) {
 
 /*
  * Callback to reset the urgent flag of the given con to false. May be started by
- * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * workspace_show to avoid urgency hints being lost by switching to a workspace
  * focusing the con.
  *
  */
@@ -360,7 +360,11 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents
     }
 }
 
-static void _workspace_show(Con *workspace) {
+/*
+ * Switches to the given workspace
+ *
+ */
+void workspace_show(Con *workspace) {
     Con *current, *old = NULL;
     Con *old_focus = focused;
 
@@ -393,10 +397,8 @@ static void _workspace_show(Con *workspace) {
      * focused) are skipped, see bug #868. */
     if (current && !con_is_internal(current)) {
         FREE(previous_workspace_name);
-        if (current) {
-            previous_workspace_name = sstrdup(current->name);
-            DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
-        }
+        previous_workspace_name = sstrdup(current->name);
+        DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
     }
 
     workspace_reassign_sticky(workspace);
@@ -487,14 +489,6 @@ static void _workspace_show(Con *workspace) {
     output_push_sticky_windows(old_focus);
 }
 
-/*
- * Switches to the given workspace
- *
- */
-void workspace_show(Con *workspace) {
-    _workspace_show(workspace);
-}
-
 /*
  * Looks up the workspace by name and switches to it.
  *
@@ -502,7 +496,7 @@ void workspace_show(Con *workspace) {
 void workspace_show_by_name(const char *num) {
     Con *workspace;
     workspace = workspace_get(num, NULL);
-    _workspace_show(workspace);
+    workspace_show(workspace);
 }
 
 /*
diff --git a/src/x.c b/src/x.c
index 7829079bb0363f8514e5b1abe6513434c0db809a..629520d4e0d2ab189e3607e06bcdae24d2cbb305 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -99,6 +99,23 @@ static con_state *state_for_frame(xcb_window_t window) {
     return NULL;
 }
 
+/*
+ * Changes the atoms on the root window and the windows themselves to properly
+ * reflect the current focus for ewmh compliance.
+ *
+ */
+static void change_ewmh_focus(xcb_window_t new_focus, xcb_window_t old_focus) {
+    ewmh_update_active_window(new_focus);
+
+    if (new_focus != XCB_WINDOW_NONE) {
+        ewmh_update_focused(new_focus, true);
+    }
+
+    if (old_focus != XCB_WINDOW_NONE) {
+        ewmh_update_focused(old_focus, false);
+    }
+}
+
 /*
  * Initializes the X11 part for the given container. Called exactly once for
  * every container from con_new().
@@ -1120,7 +1137,7 @@ void x_push_changes(Con *con) {
                      to_focus, focused, focused->name);
                 send_take_focus(to_focus, last_timestamp);
 
-                ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+                change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
 
                 if (to_focus != last_focused && is_con_attached(focused))
                     ipc_send_window_event("focus", focused);
@@ -1139,7 +1156,7 @@ void x_push_changes(Con *con) {
                     xcb_change_window_attributes(conn, focused->window->id, XCB_CW_EVENT_MASK, values);
                 }
 
-                ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
+                change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused);
 
                 if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
                     ipc_send_window_event("focus", focused);
@@ -1154,7 +1171,8 @@ void x_push_changes(Con *con) {
          * root window in order to avoid an X11 fallback mechanism causing a ghosting effect (see #1378). */
         DLOG("Still no window focused, better set focus to the EWMH support window (%d)\n", ewmh_window);
         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, ewmh_window, last_timestamp);
-        ewmh_update_active_window(XCB_WINDOW_NONE);
+        change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
+
         focused_id = ewmh_window;
     }
 
index e754c0c17f6bf3b8b5b92e72233ebbe05ab8198a..68ac1ee569827edbef609ff00c1f388d1a7a8d9b 100644 (file)
@@ -51,6 +51,7 @@ our @EXPORT = qw(
     kill_all_windows
     events_for
     listen_for_binding
+    is_net_wm_state_focused
 );
 
 =head1 NAME
@@ -1026,6 +1027,40 @@ sub listen_for_binding {
     return $command;
 }
 
+=head2 is_net_wm_state_focused
+
+Returns true if the given window has the _NET_WM_STATE_FOCUSED atom.
+
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
+
+=cut
+sub is_net_wm_state_focused {
+    my ($window) = @_;
+
+    sync_with_i3;
+    my $atom = $x->atom(name => '_NET_WM_STATE_FOCUSED');
+    my $cookie = $x->get_property(
+        0,
+        $window->{id},
+        $x->atom(name => '_NET_WM_STATE')->id,
+        GET_PROPERTY_TYPE_ANY,
+        0,
+        4096
+    );
+
+    my $reply = $x->get_property_reply($cookie->{sequence});
+    my $len = $reply->{length};
+    return 0 if $len == 0;
+
+    my @atoms = unpack("L$len", $reply->{value});
+    for (my $i = 0; $i < $len; $i++) {
+        return 1 if $atoms[$i] == $atom->id;
+    }
+
+    return 0;
+}
+
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index 41d400d5cd15cc34f91ff6822273b56f3ea810b0..c4b42964fc3875580b2dc40e6ee691785e13ce0a 100644 (file)
@@ -55,6 +55,7 @@ subtest 'Window without WM_TAKE_FOCUS', sub {
     my $window = open_window;
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
@@ -91,6 +92,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub {
     $window->map;
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
@@ -112,6 +114,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub {
     my $window = open_window({ protocols => [ $take_focus ] });
 
     ok(!recv_take_focus($window), 'did not receive ClientMessage');
+    ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set');
 
     my ($nodes) = get_ws_content($ws);
     my $con = shift @$nodes;
index 392beae1330e456dc00bc64ae3f20eed708db422..3a3e7c6e8bcf8a87b7686c3c43586bbba2aa839a 100644 (file)
@@ -36,7 +36,7 @@ sub get_wm_state {
     return undef if $len == 0;
 
     my @atoms = unpack("L$len", $reply->{value});
-    return \@atoms;
+    return @atoms;
 }
 
 my $wm_state_sticky = $x->atom(name => '_NET_WM_STATE_STICKY')->id;
@@ -51,18 +51,24 @@ my $wm_state_fullscreen = $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id;
 fresh_workspace;
 my $window = open_window;
 cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+my @state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, 'sanity check: _NET_WM_STATE_STICKY is set');
 
 cmd 'fullscreen enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky, $wm_state_fullscreen ],
-    'both _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_STICKY are set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
 
 cmd 'sticky disable';
-is_deeply(get_wm_state($window), [ $wm_state_fullscreen ], 'only _NET_WM_STATE_FULLSCREEN is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) == 0, '_NET_WM_STATE_STICKY is not set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) > 0, '_NET_WM_STATE_FULLSCREEN is set');
 
 cmd 'sticky enable';
 cmd 'fullscreen disable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
+ok((scalar grep { $_ == $wm_state_fullscreen } @state) == 0, '_NET_WM_STATE_FULLSCREEN is not set');
 
 ###############################################################################
 # _NET_WM_STATE is removed when the window is withdrawn.
@@ -71,7 +77,8 @@ is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'only _NET_WM_STATE_STICK
 fresh_workspace;
 $window = open_window;
 cmd 'sticky enable';
-is_deeply(get_wm_state($window), [ $wm_state_sticky ], 'sanity check: _NET_WM_STATE_STICKY is set');
+@state = get_wm_state($window);
+ok((scalar grep { $_ == $wm_state_sticky } @state) > 0, '_NET_WM_STATE_STICKY is set');
 
 $window->unmap;
 wait_for_unmap($window);
index 0f11624134eda4f77aca53e729e6b2211885f5ab..71f19ded7e907fc73af0623e63d7bdc88f0375f0 100644 (file)
@@ -27,6 +27,7 @@ sub kill_and_confirm_focus {
 }
 
 my @windows;
+my $ws;
 
 sub focus_windows {
     for (my $i = $#windows; $i >= 0; $i--) {
@@ -122,4 +123,22 @@ $windows[0] = open_window;
 cmd '[id=' . $windows[3]->id . '] move right';
 confirm_focus('split-v + unfocused move');
 
+######################################################################
+# Test that moving an unfocused container from inside a split
+# container to another workspace doesn't focus sibling.
+######################################################################
+
+$ws = fresh_workspace;
+$windows[0] = open_window;
+$windows[1] = open_window;
+cmd 'split v';
+open_window;
+cmd 'mark a';
+
+cmd '[id=' . $windows[0]->id . '] focus';
+cmd '[con_mark=a] move to workspace ' . get_unused_workspace;
+
+is(@{get_ws_content($ws)}, 2, 'Sanity check: marked window moved');
+confirm_focus('Move unfocused window from split container');
+
 done_testing;
diff --git a/testcases/t/295-net-wm-state-focused.t b/testcases/t/295-net-wm-state-focused.t
new file mode 100644 (file)
index 0000000..1881154
--- /dev/null
@@ -0,0 +1,37 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Tests for setting and removing the _NET_WM_STATE_FOCUSED atom properly.
+# Ticket: #2273
+use i3test;
+use X11::XCB qw(:all);
+
+my ($windowA, $windowB);
+
+fresh_workspace;
+$windowA = open_window;
+
+ok(is_net_wm_state_focused($windowA), 'a newly opened window that is focused should have _NET_WM_STATE_FOCUSED set');
+
+$windowB = open_window;
+
+ok(!is_net_wm_state_focused($windowA), 'when a another window is focused, the old window should not have _NET_WM_STATE_FOCUSED set');
+
+fresh_workspace;
+
+ok(!is_net_wm_state_focused($windowB), 'when focus moves to the ewmh support window, neither window should have _NET_WM_STATE_FOCUSED set');
+
+done_testing;
diff --git a/testcases/t/296-regress-focus-behind-fullscreen-floating.t b/testcases/t/296-regress-focus-behind-fullscreen-floating.t
new file mode 100644 (file)
index 0000000..0867f08
--- /dev/null
@@ -0,0 +1,40 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • https://build.i3wm.org/docs/testsuite.html
+#   (or docs/testsuite)
+#
+# • https://build.i3wm.org/docs/lib-i3test.html
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • https://build.i3wm.org/docs/ipc.html
+#   (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+#   (unless you are already familiar with Perl)
+#
+# Test that directional focus gives focus to floating fullscreen containers when
+# switching workspaces.
+# Ticket: #3201
+# Bug still in: 4.15-59-gb849fe3e
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+EOT
+
+fresh_workspace(output => 0);
+my $ws = fresh_workspace(output => 1);
+open_window;
+open_floating_window;
+cmd 'fullscreen enable';
+my $expected_focus = get_focused($ws);
+
+cmd 'focus left';
+cmd 'focus right';
+
+is (get_focused($ws), $expected_focus, 'floating fullscreen window focused after directional focus');
+
+done_testing;
index 5bcdb09afe8f59c756286bdd90e370998b46929b..3db8c4f01a005e5bb4da0e175fa51b417b357133 100644 (file)
@@ -97,4 +97,32 @@ is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom wo
 $compare_window = shift @{get_ws_content('left-top')};
 is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
 
+#####################################################################
+# Moving a fullscreen container should change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+my $fs_window = open_window;
+open_window;
+
+cmd '[id=' . $fs_window->id . '] fullscreen enable, move right';
+is(scalar @{get_ws_content('right-top')}, 1, 'moved fullscreen window to right-top workspace');
+
+#####################################################################
+# Moving a global fullscreen container should not change its output.
+#####################################################################
+
+kill_all_windows;
+
+cmd 'workspace left-top';
+open_window;
+open_window;
+open_window;
+
+cmd 'fullscreen global, move right, fullscreen disable';
+is(scalar @{get_ws_content('right-top')}, 0, 'global fullscreen window didn\'t change workspace with move');
+
 done_testing;