]> git.sur5r.net Git - i3/i3/blobdiff - src/manage.c
Merge branch 'master' into next
[i3/i3] / src / manage.c
index 35055d17ea04e7787efe40dde42e6cb6fdf3e751..ff7fdc6d09bae81c641cbc66162667995f00ec91 100644 (file)
@@ -1,14 +1,14 @@
+#undef I3__FILE__
+#define I3__FILE__ "manage.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
- * manage.c: Contains all functions for initially managing new windows
- *           (or existing ones on restart).
+ * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-
 #include "all.h"
 
 /*
@@ -49,7 +49,7 @@ void manage_existing_windows(xcb_window_t root) {
  * side-effects which are to be expected when continuing to run i3.
  *
  */
-void restore_geometry() {
+void restore_geometry(void) {
     DLOG("Restoring geometry\n");
 
     Con *con;
@@ -64,8 +64,12 @@ void restore_geometry() {
                                 con->rect.x, con->rect.y);
         }
 
+    /* Strictly speaking, this line doesn’t really belong here, but since we
+     * are syncing, let’s un-register as a window manager first */
+    xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
+
     /* Make sure our changes reach the X server, we restart/exit now */
-    xcb_flush(conn);
+    xcb_aux_sync(conn);
 }
 
 /*
@@ -79,12 +83,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     xcb_get_geometry_reply_t *geom;
     xcb_get_window_attributes_reply_t *attr = NULL;
 
-    DLOG("---> looking at window 0x%08x\n", window);
-
     xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
                               utf8_title_cookie, title_cookie,
                               class_cookie, leader_cookie, transient_cookie,
-                              role_cookie;
+                              role_cookie, startup_id_cookie, wm_hints_cookie;
 
 
     geomc = xcb_get_geometry(conn, d);
@@ -102,13 +104,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     }
 
     if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
-        DLOG("map_state unviewable\n");
         FREE_GEOMETRY();
         goto out;
     }
 
     /* Don’t manage clients with the override_redirect flag */
-    DLOG("override_redirect is %d\n", attr->override_redirect);
     if (attr->override_redirect) {
         FREE_GEOMETRY();
         goto out;
@@ -130,11 +130,22 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     uint32_t values[1];
 
     /* Set a temporary event mask for the new window, consisting only of
-     * PropertyChange. We need to be notified of PropertyChanges because the
-     * client can change its properties *after* we requested them but *before*
-     * we actually reparented it and have set our final event mask. */
-    values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
-    xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
+     * PropertyChange and StructureNotify. We need to be notified of
+     * PropertyChanges because the client can change its properties *after* we
+     * requested them but *before* we actually reparented it and have set our
+     * final event mask.
+     * We need StructureNotify because the client may unmap the window before
+     * we get to re-parent it.
+     * If this request fails, we assume the client has already unmapped the
+     * window between the MapRequest and our event mask change. */
+    values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+                XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+    xcb_void_cookie_t event_mask_cookie =
+        xcb_change_window_attributes_checked(conn, window, XCB_CW_EVENT_MASK, values);
+    if (xcb_request_check(conn, event_mask_cookie) != NULL) {
+        LOG("Could not change event mask, the window probably already disappeared.\n");
+        goto out;
+    }
 
 #define GET_PROPERTY(atom, len) xcb_get_property(conn, false, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, len)
 
@@ -147,12 +158,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
     class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
     role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
+    startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512);
+    wm_hints_cookie = xcb_icccm_get_wm_hints(conn, window);
     /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
 
-    DLOG("reparenting!\n");
+    DLOG("Managing window 0x%08x\n", window);
 
     i3Window *cwindow = scalloc(sizeof(i3Window));
     cwindow->id = window;
+    cwindow->depth = get_visual_depth(attr->visual);
 
     /* We need to grab the mouse buttons for click to focus */
     xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
@@ -162,9 +176,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
 
     xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
                     XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
-                    3 /* right mouse button */,
+                    2 /* middle mouse button */,
                     XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
 
+    xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS,
+                    XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
+                    3 /* right mouse button */,
+                    XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
 
     /* update as much information as possible so far (some replies may be NULL) */
     window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true);
@@ -174,6 +192,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
     window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
     window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
+    window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL));
+
+    xcb_get_property_reply_t *startup_id_reply;
+    startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL);
+    char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply);
+    DLOG("startup workspace = %s\n", startup_ws);
 
     /* check if the window needs WM_TAKE_FOCUS */
     cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
@@ -214,7 +238,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     DLOG("Initial geometry: (%d, %d, %d, %d)\n", geom->x, geom->y, geom->width, geom->height);
 
     Con *nc = NULL;
-    Match *match;
+    Match *match = NULL;
     Assignment *assignment;
 
     /* TODO: two matches for one container */
@@ -233,6 +257,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
                 else nc = tree_open_con(nc->parent, cwindow);
             }
         /* TODO: handle assignments with type == A_TO_OUTPUT */
+        } else if (startup_ws) {
+            /* If it’s not assigned, but was started on a specific workspace,
+             * we want to open it there */
+            DLOG("Using workspace on which this application was started (%s)\n", startup_ws);
+            nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL));
+            DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name);
+            if (nc->type == CT_WORKSPACE)
+                nc = tree_open_con(nc, cwindow);
+            else nc = tree_open_con(nc->parent, cwindow);
         } else {
             /* If not, insert it at the currently focused position */
             if (focused->type == CT_CON && con_accepts_window(focused)) {
@@ -256,7 +289,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     nc->border_width = geom->border_width;
 
     char *name;
-    asprintf(&name, "[i3 con] container around %p", cwindow);
+    sasprintf(&name, "[i3 con] container around %p", cwindow);
     x_set_name(nc, name);
     free(name);
 
@@ -268,10 +301,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     if (fs == NULL) {
         DLOG("Not in fullscreen mode, focusing\n");
         if (!cwindow->dock) {
-            /* Check that the workspace is visible. If the window was assigned
-             * to an invisible workspace, we should not steal focus. */
-            if (workspace_is_visible(ws)) {
-                con_focus(nc);
+            /* Check that the workspace is visible and on the same output as
+             * the current focused container. If the window was assigned to an
+             * invisible workspace, we should not steal focus. */
+            Con *current_output = con_get_output(focused);
+            Con *target_output = con_get_output(ws);
+
+            if (workspace_is_visible(ws) && current_output == target_output) {
+                if (!match || !match->restart_mode) {
+                    con_focus(nc);
+                } else DLOG("not focusing, matched with restart_mode == true\n");
             } else DLOG("workspace not visible, not focusing\n");
         } else DLOG("dock, not focusing\n");
     } else {
@@ -305,13 +344,19 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         (cwindow->leader != XCB_NONE &&
          cwindow->leader != cwindow->id &&
          con_by_window_id(cwindow->leader) != NULL)) {
-        LOG("This window is transiert for another window, setting floating\n");
+        LOG("This window is transient for another window, setting floating\n");
         want_floating = true;
 
         if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
             fs != NULL) {
             LOG("There is a fullscreen window, leaving fullscreen mode\n");
             con_toggle_fullscreen(fs, CF_OUTPUT);
+        } else if (config.popup_during_fullscreen == PDF_SMART &&
+                   fs != NULL &&
+                   fs->window != NULL &&
+                   fs->window->id == cwindow->transient_for) {
+            LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
+            con_focus(nc);
         }
     }
 
@@ -361,6 +406,26 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     /* Check if any assignments match */
     run_assignments(cwindow);
 
+    /* 'ws' may be invalid because of the assignments, e.g. when the user uses
+     * "move window to workspace 1", but had it assigned to workspace 2. */
+    ws = con_get_workspace(nc);
+
+    /* If this window was put onto an invisible workspace (via assignments), we
+     * render this workspace. It wouldn’t be rendered in our normal code path
+     * because only the visible workspaces get rendered.
+     *
+     * By rendering the workspace, we assign proper coordinates (read: not
+     * width=0, height=0) to the window, which is important for windows who
+     * actually use them to position their GUI elements, e.g. rhythmbox. */
+    if (ws && !workspace_is_visible(ws)) {
+        /* This is a bit hackish: we need to copy the content container’s rect
+         * to the workspace, because calling render_con() on the content
+         * container would also take the shortcut and not render the invisible
+         * workspace at all. However, just calling render_con() on the
+         * workspace isn’t enough either — it needs the rect. */
+        ws->rect = ws->parent->rect;
+        render_con(ws, true);
+    }
     tree_render();
 
 geom_out: