If a window places itself at (0,0) and there are struts there, assume it is a bug...
[dana/openbox.git] / openbox / client.c
index 67d2290..6d934bd 100644 (file)
@@ -31,6 +31,7 @@
 #include "grab.h"
 #include "prompt.h"
 #include "focus.h"
+#include "focus_cycle.h"
 #include "stacking.h"
 #include "openbox.h"
 #include "group.h"
@@ -38,7 +39,7 @@
 #include "menuframe.h"
 #include "keyboard.h"
 #include "mouse.h"
-#include "render/render.h"
+#include "obrender/render.h"
 #include "gettext.h"
 #include "obt/display.h"
 #include "obt/prop.h"
@@ -75,11 +76,11 @@ static RrImage *client_default_icon     = NULL;
 static void client_get_all(ObClient *self, gboolean real);
 static void client_get_startup_id(ObClient *self);
 static void client_get_session_ids(ObClient *self);
+static void client_save_app_rule_values(ObClient *self);
 static void client_get_area(ObClient *self);
 static void client_get_desktop(ObClient *self);
 static void client_get_state(ObClient *self);
 static void client_get_shaped(ObClient *self);
-static void client_get_mwm_hints(ObClient *self);
 static void client_get_colormap(ObClient *self);
 static void client_set_desktop_recursive(ObClient *self,
                                          guint target,
@@ -106,7 +107,8 @@ static GSList *client_search_all_top_parents_internal(ObClient *self,
 static void client_call_notifies(ObClient *self, GSList *list);
 static void client_ping_event(ObClient *self, gboolean dead);
 static void client_prompt_kill(ObClient *self);
-
+static gboolean client_can_steal_focus(ObClient *self, Time steal_time,
+                                       Time launch_time);
 
 void client_startup(gboolean reconfig)
 {
@@ -200,8 +202,10 @@ void client_manage(Window window, ObPrompt *prompt)
     gboolean activate = FALSE;
     ObAppSettings *settings;
     gboolean transient = FALSE;
-    Rect place, *monitor;
+    Rect place, *monitor, *allmonitors;
     Time launch_time, map_time;
+    guint32 user_time;
+    gboolean obplaced;
 
     ob_debug("Managing window: 0x%lx", window);
 
@@ -221,6 +225,7 @@ void client_manage(Window window, ObPrompt *prompt)
     self->obwin.type = OB_WINDOW_CLASS_CLIENT;
     self->window = window;
     self->prompt = prompt;
+    self->managed = TRUE;
 
     /* non-zero defaults */
     self->wmstate = WithdrawnState; /* make sure it gets updated first time */
@@ -232,6 +237,13 @@ void client_manage(Window window, ObPrompt *prompt)
 
     ob_debug("Window type: %d", self->type);
     ob_debug("Window group: 0x%x", self->group?self->group->leader:0);
+    ob_debug("Window name: %s class: %s role: %s title: %s",
+             self->name, self->class, self->role, self->title);
+
+    /* per-app settings override stuff from client_get_all, and return the
+       settings for other uses too. the returned settings is a shallow copy,
+       that needs to be freed with g_free(). */
+    settings = client_get_settings_state(self);
 
     /* now we have all of the window's information so we can set this up.
        do this before creating the frame, so it can tell that we are still
@@ -253,16 +265,15 @@ void client_manage(Window window, ObPrompt *prompt)
        time now */
     grab_server(FALSE);
 
-    /* per-app settings override stuff from client_get_all, and return the
-       settings for other uses too. the returned settings is a shallow copy,
-       that needs to be freed with g_free(). */
-    settings = client_get_settings_state(self);
     /* the session should get the last say though */
     client_restore_session_state(self);
 
     /* tell startup notification that this app started */
     launch_time = sn_app_started(self->startup_id, self->class, self->name);
 
+    if (!OBT_PROP_GET32(self->window, NET_WM_USER_TIME, CARDINAL, &user_time))
+        user_time = map_time;
+
     /* do this after we have a frame.. it uses the frame to help determine the
        WM_STATE to apply. */
     client_change_state(self);
@@ -279,9 +290,13 @@ void client_manage(Window window, ObPrompt *prompt)
         /* this means focus=true for window is same as config_focus_new=true */
         ((config_focus_new || (settings && settings->focus == 1)) ||
          client_search_focus_tree_full(self)) &&
+        /* NET_WM_USER_TIME 0 when mapping means don't focus */
+        (user_time != 0) &&
         /* this checks for focus=false for the window */
         (!settings || settings->focus != 0) &&
-        focus_valid_target(self, FALSE, FALSE, TRUE, FALSE, FALSE))
+        focus_valid_target(self, self->desktop,
+                           FALSE, FALSE, TRUE, FALSE, FALSE,
+                           settings->focus == 1))
     {
         activate = TRUE;
     }
@@ -297,6 +312,7 @@ void client_manage(Window window, ObPrompt *prompt)
     /* where the frame was placed is where the window was originally */
     place = self->area;
     monitor = screen_physical_area_monitor(screen_find_monitor(&place));
+    allmonitors = screen_physical_area_all_monitors();
 
     /* figure out placement for the window if the window is new */
     if (ob_state() == OB_STATE_RUNNING) {
@@ -316,9 +332,23 @@ void client_manage(Window window, ObPrompt *prompt)
                      "program + user specified" :
                      "BADNESS !?")))), place.width, place.height);
 
-        /* splash screens are also returned as TRUE for transient,
-           and so will be forced on screen below */
-        transient = place_client(self, &place.x, &place.y, settings);
+        obplaced = place_client(self, &place.x, &place.y, settings);
+
+        /* watch for buggy apps that ask to be placed at (0,0) when there is
+           a strut there */
+        if (!obplaced && place.x == 0 && place.y == 0 &&
+            /* oldschool fullscreen windows are allowed */
+            !(self->decorations == 0 && (RECT_EQUAL(place, *monitor) ||
+                                         RECT_EQUAL(place, *allmonitors))))
+        {
+            Rect *r;
+
+            r = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS, NULL);
+            place.x = r->x;
+            place.y = r->y;
+            ob_debug("Moving buggy app from (0,0) to (%d,%d)", r->x, r->y);
+            g_free(r);
+        }
 
         /* make sure the window is visible. */
         client_find_onscreen(self, &place.x, &place.y,
@@ -334,11 +364,13 @@ void client_manage(Window window, ObPrompt *prompt)
                                 it is up to the placement routines to avoid
                                 the xinerama divides)
 
-                                splash screens get "transient" set to TRUE by
-                                the place_client call
+                                children and splash screens are forced on
+                                screen, but i don't remember why i decided to
+                                do that.
                              */
                              ob_state() == OB_STATE_RUNNING &&
-                             (transient ||
+                             (self->type == OB_CLIENT_TYPE_DIALOG ||
+                              self->type == OB_CLIENT_TYPE_SPLASH ||
                               (!((self->positioned & USPosition) ||
                                  (settings && settings->pos_given)) &&
                                client_normal(self) &&
@@ -348,7 +380,8 @@ void client_manage(Window window, ObPrompt *prompt)
                                   makes its fullscreen window fit the screen
                                   but it is not USSize'd or USPosition'd) */
                                !(self->decorations == 0 &&
-                                 RECT_EQUAL(place, *monitor)))));
+                                 (RECT_EQUAL(place, *monitor) ||
+                                  RECT_EQUAL(place, *allmonitors))))));
     }
 
     /* if the window isn't user-sized, then make it fit inside
@@ -368,7 +401,8 @@ void client_manage(Window window, ObPrompt *prompt)
           /* don't shrink oldschool fullscreen windows to fit inside the
              struts (fixes Acroread, which makes its fullscreen window
              fit the screen but it is not USSize'd or USPosition'd) */
-          !(self->decorations == 0 && RECT_EQUAL(place, *monitor)))))
+          !(self->decorations == 0 && (RECT_EQUAL(place, *monitor) ||
+                                       RECT_EQUAL(place, *allmonitors))))))
     {
         Rect *a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &place);
 
@@ -408,107 +442,20 @@ void client_manage(Window window, ObPrompt *prompt)
 
     g_free(monitor);
     monitor = NULL;
+    g_free(allmonitors);
+    allmonitors = NULL;
 
     ob_debug_type(OB_DEBUG_FOCUS, "Going to try activate new window? %s",
                   activate ? "yes" : "no");
     if (activate) {
-        gboolean raise = FALSE;
-
-        /* This is focus stealing prevention */
-        ob_debug_type(OB_DEBUG_FOCUS,
-                      "Want to focus new window 0x%x at time %u "
-                      "launched at %u (last user interaction time %u)",
-                      self->window, map_time, launch_time,
-                      event_last_user_time);
-
-        if (menu_frame_visible || moveresize_in_progress) {
-            activate = FALSE;
-            raise = TRUE;
-            ob_debug_type(OB_DEBUG_FOCUS,
-                          "Not focusing the window because the user is inside "
-                          "an Openbox menu or is move/resizing a window and "
-                          "we don't want to interrupt them");
-        }
-
-        /* if it's on another desktop */
-        else if (!(self->desktop == screen_desktop ||
-                   self->desktop == DESKTOP_ALL) &&
-                 /* the timestamp is from before you changed desktops */
-                 launch_time && screen_desktop_user_time &&
-                 !event_time_after(launch_time, screen_desktop_user_time))
-        {
-            activate = FALSE;
-            raise = TRUE;
-            ob_debug_type(OB_DEBUG_FOCUS,
-                          "Not focusing the window because its on another "
-                          "desktop");
-        }
-        /* If something is focused, and it's not our relative... */
-        else if (focus_client && client_search_focus_tree_full(self) == NULL &&
-                 client_search_focus_group_full(self) == NULL)
-        {
-            /* If the user is working in another window right now, then don't
-               steal focus */
-            if (event_last_user_time && launch_time &&
-                event_time_after(event_last_user_time, launch_time) &&
-                event_last_user_time != launch_time &&
-                event_time_after(event_last_user_time,
-                                 map_time - OB_EVENT_USER_TIME_DELAY))
-            {
-                activate = FALSE;
-                ob_debug_type(OB_DEBUG_FOCUS,
-                              "Not focusing the window because the user is "
-                              "working in another window");
-            }
-            /* If it's a transient (and its parents aren't focused) */
-            else if (client_has_parent(self)) {
-                activate = FALSE;
-                ob_debug_type(OB_DEBUG_FOCUS,
-                              "Not focusing the window because it is a "
-                              "transient, and its relatives aren't focused");
-            }
-            /* Don't steal focus from globally active clients.
-               I stole this idea from KWin. It seems nice.
-             */
-            else if (!(focus_client->can_focus ||
-                       focus_client->focus_notify))
-            {
-                activate = FALSE;
-                ob_debug_type(OB_DEBUG_FOCUS,
-                              "Not focusing the window because a globally "
-                              "active client has focus");
-            }
-            /* Don't move focus if it's not going to go to this window
-               anyway */
-            else if (client_focus_target(self) != self) {
-                activate = FALSE;
-                raise = TRUE;
-                ob_debug_type(OB_DEBUG_FOCUS,
-                              "Not focusing the window because another window "
-                              "would get the focus anyway");
-            }
-            else if (!(self->desktop == screen_desktop ||
-                       self->desktop == DESKTOP_ALL))
-            {
-                activate = FALSE;
-                raise = TRUE;
-                ob_debug_type(OB_DEBUG_FOCUS,
-                              "Not focusing the window because it is on "
-                              "another desktop and no relatives are focused ");
-            }
-        }
+        activate = client_can_steal_focus(self, map_time, launch_time);
 
         if (!activate) {
-            ob_debug_type(OB_DEBUG_FOCUS,
-                          "Focus stealing prevention activated for %s at "
-                          "time %u (last user interaction time %u)",
-                          self->title, map_time, event_last_user_time);
-            /* if the client isn't focused, then hilite it so the user
-               knows it is there */
-            client_hilite(self, TRUE);
-            /* we may want to raise it even tho we're not activating it */
-            if (raise && !client_restore_session_stacking(self))
-                stacking_raise(CLIENT_AS_WINDOW(self));
+            /* if the client isn't stealing focus, then hilite it so the user
+               knows it is there, but don't do this if we're restoring from a
+               session */
+            if (!client_restore_session_stacking(self))
+                client_hilite(self, TRUE);
         }
     }
     else {
@@ -562,7 +509,6 @@ void client_manage(Window window, ObPrompt *prompt)
              window, self->frame->window, self->class);
 }
 
-
 ObClient *client_fake_manage(Window window)
 {
     ObClient *self;
@@ -630,6 +576,8 @@ void client_unmanage(ObClient *self)
 
     mouse_grab_for_client(self, FALSE);
 
+    self->managed = FALSE;
+
     /* remove the window from our save set, unless we are managing an internal
        ObPrompt window */
     if (!self->prompt)
@@ -758,6 +706,105 @@ void client_fake_unmanage(ObClient *self)
     g_free(self);
 }
 
+static gboolean client_can_steal_focus(ObClient *self, Time steal_time,
+                                       Time launch_time)
+{
+    gboolean steal;
+    gboolean relative_focused;
+    gboolean parent_focused;
+
+    steal = TRUE;
+
+    parent_focused = (focus_client != NULL &&
+                      client_search_focus_parent(self));
+    relative_focused = (focus_client != NULL &&
+                        (client_search_focus_tree_full(self) != NULL ||
+                         client_search_focus_group_full(self) != NULL));
+
+    /* This is focus stealing prevention */
+    ob_debug_type(OB_DEBUG_FOCUS,
+                  "Want to focus new window 0x%x at time %u "
+                  "launched at %u (last user interaction time %u)",
+                  self->window, steal_time, launch_time,
+                  event_last_user_time);
+
+    /* if it's on another desktop */
+    if (!(self->desktop == screen_desktop ||
+          self->desktop == DESKTOP_ALL) &&
+        /* the timestamp is from before you changed desktops */
+        launch_time && screen_desktop_user_time &&
+        !event_time_after(launch_time, screen_desktop_user_time))
+    {
+        steal = FALSE;
+        ob_debug_type(OB_DEBUG_FOCUS,
+                      "Not focusing the window because its on another "
+                      "desktop\n");
+    }
+    /* If something is focused... */
+    else if (focus_client) {
+        /* If the user is working in another window right now, then don't
+           steal focus */
+        if (!parent_focused &&
+            event_last_user_time && launch_time &&
+            event_time_after(event_last_user_time, launch_time) &&
+            event_last_user_time != launch_time &&
+            event_time_after(event_last_user_time,
+                             steal_time - OB_EVENT_USER_TIME_DELAY))
+        {
+            steal = FALSE;
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Not focusing the window because the user is "
+                          "working in another window that is not "
+                          "its parent");
+        }
+        /* If the new window is a transient (and its relatives aren't
+           focused) */
+        else if (client_has_parent(self) && !relative_focused) {
+            steal = FALSE;
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Not focusing the window because it is a "
+                          "transient, and its relatives aren't focused");
+        }
+        /* Don't steal focus from globally active clients.
+           I stole this idea from KWin. It seems nice.
+        */
+        else if (!(focus_client->can_focus ||
+                   focus_client->focus_notify))
+        {
+            steal = FALSE;
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Not focusing the window because a globally "
+                          "active client has focus");
+        }
+        /* Don't move focus if it's not going to go to this window
+           anyway */
+        else if (client_focus_target(self) != self) {
+            steal = FALSE;
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Not focusing the window because another window "
+                          "would get the focus anyway");
+        }
+        /* Don't move focus if the window is not visible on the current
+           desktop and none of its relatives are focused */
+        else if (!(self->desktop == screen_desktop ||
+                   self->desktop == DESKTOP_ALL) &&
+                 !relative_focused)
+        {
+            steal = FALSE;
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Not focusing the window because it is on "
+                          "another desktop and no relatives are focused ");
+        }
+    }
+
+    if (!steal)
+        ob_debug_type(OB_DEBUG_FOCUS,
+                      "Focus stealing prevention activated for %s at "
+                      "time %u (last user interaction time %u)",
+                      self->title, steal_time, event_last_user_time);
+    return steal;
+}
+
 /*! Returns a new structure containing the per-app settings for this client.
   The returned structure needs to be freed with g_free. */
 static ObAppSettings *client_get_settings_state(ObClient *self)
@@ -771,10 +818,10 @@ static ObAppSettings *client_get_settings_state(ObClient *self)
         ObAppSettings *app = it->data;
         gboolean match = TRUE;
 
-        g_assert(app->name != NULL || app->class != NULL);
+        g_assert(app->name != NULL || app->class != NULL ||
+                 app->role != NULL || app->title != NULL ||
+                 (signed)app->type >= 0);
 
-        /* we know that either name or class is not NULL so it will have to
-           match to use the rule */
         if (app->name &&
             !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
             match = FALSE;
@@ -786,8 +833,13 @@ static ObAppSettings *client_get_settings_state(ObClient *self)
                  !g_pattern_match(app->role,
                                   strlen(self->role), self->role, NULL))
             match = FALSE;
-        else if ((signed)app->type >= 0 && app->type != self->type)
+        else if (app->title &&
+                 !g_pattern_match(app->title,
+                                  strlen(self->title), self->title, NULL))
+            match = FALSE;
+        else if ((signed)app->type >= 0 && app->type != self->type) {
             match = FALSE;
+        }
 
         if (match) {
             ob_debug("Window matching: %s", app->name);
@@ -1066,6 +1118,9 @@ static void client_get_all(ObClient *self, gboolean real)
     /* get this early so we have it for debugging */
     client_update_title(self);
 
+    /* save the values of the variables used for app rule matching */
+    client_save_app_rule_values(self);
+
     client_update_protocols(self);
 
     client_update_wmhints(self);
@@ -1286,7 +1341,6 @@ static void client_update_transient_tree(ObClient *self,
       transient windows as their children.
       * * */
 
-
     /* No change has occured */
     if (oldgroup == newgroup &&
         oldgtran == newgtran &&
@@ -1380,7 +1434,7 @@ static void client_update_transient_tree(ObClient *self,
     }
 }
 
-static void client_get_mwm_hints(ObClient *self)
+void client_get_mwm_hints(ObClient *self)
 {
     guint num;
     guint32 *hints;
@@ -1730,7 +1784,8 @@ void client_setup_decor_and_functions(ObClient *self, gboolean reconfig)
     /* finally, the user can have requested no decorations, which overrides
        everything (but doesnt give it a border if it doesnt have one) */
     if (self->undecorated)
-        self->decorations = 0;
+        self->decorations &= (config_theme_keepborder ?
+                              OB_FRAME_DECOR_BORDER : 0);
 
     /* if we don't have a titlebar, then we cannot shade! */
     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
@@ -1891,6 +1946,8 @@ void client_update_wmhints(ObClient *self)
 
         XFree(hints);
     }
+
+    focus_cycle_addremove(self, TRUE);
 }
 
 void client_update_title(ObClient *self)
@@ -2014,7 +2071,7 @@ void client_update_strut(ObClient *self)
         STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
                           0, 0, 0, 0, 0, 0, 0, 0);
 
-    if (!STRUT_EQUAL(strut, self->strut)) {
+    if (!PARTIAL_STRUT_EQUAL(strut, self->strut)) {
         self->strut = strut;
 
         /* updating here is pointless while we're being mapped cuz we're not in
@@ -2102,7 +2159,6 @@ void client_update_icons(ObClient *self)
                                        (gint*)&w, (gint*)&h, &data);
                 obt_display_ignore_errors(FALSE);
 
-
                 if (xicon) {
                     if (w > 0 && h > 0) {
                         /* is this icon in the cache yet? */
@@ -2272,6 +2328,39 @@ static void client_get_session_ids(ObClient *self)
     }
 }
 
+/*! Save the properties used for app matching rules, as seen by Openbox when
+  the window mapped, so that users can still access them later if the app
+  changes them */
+static void client_save_app_rule_values(ObClient *self)
+{
+    const gchar *type;
+
+    OBT_PROP_SETS(self->window, OB_APP_ROLE, utf8, self->role);
+    OBT_PROP_SETS(self->window, OB_APP_NAME, utf8, self->name);
+    OBT_PROP_SETS(self->window, OB_APP_CLASS, utf8, self->class);
+    OBT_PROP_SETS(self->window, OB_APP_TITLE, utf8, self->original_title);
+
+    switch (self->type) {
+    case OB_CLIENT_TYPE_NORMAL:
+        type = "normal"; break;
+    case OB_CLIENT_TYPE_DIALOG:
+        type = "dialog"; break;
+    case OB_CLIENT_TYPE_UTILITY:
+        type = "utility"; break;
+    case OB_CLIENT_TYPE_MENU:
+        type = "menu"; break;
+    case OB_CLIENT_TYPE_TOOLBAR:
+        type = "toolbar"; break;
+    case OB_CLIENT_TYPE_SPLASH:
+        type = "splash"; break;
+    case OB_CLIENT_TYPE_DESKTOP:
+        type = "desktop"; break;
+    case OB_CLIENT_TYPE_DOCK:
+        type = "dock"; break;
+    }
+    OBT_PROP_SETS(self->window, OB_APP_TYPE, utf8, type);
+}
+
 static void client_change_wm_state(ObClient *self)
 {
     gulong state[2];
@@ -2351,7 +2440,7 @@ ObClient *client_search_focus_tree_full(ObClient *self)
 
         for (it = self->parents; it; it = g_slist_next(it)) {
             ObClient *c = it->data;
-            if ((c = client_search_focus_tree_full(it->data))) return c;
+            if ((c = client_search_focus_tree_full(c))) return c;
         }
 
         return NULL;
@@ -2389,9 +2478,10 @@ gboolean client_has_parent(ObClient *self)
 static ObStackingLayer calc_layer(ObClient *self)
 {
     ObStackingLayer l;
-    Rect *monitor;
+    Rect *monitor, *allmonitors;
 
     monitor = screen_physical_area_monitor(client_monitor(self));
+    allmonitors = screen_physical_area_all_monitors();
 
     if (self->type == OB_CLIENT_TYPE_DESKTOP)
         l = OB_STACKING_LAYER_DESKTOP;
@@ -2405,7 +2495,8 @@ static ObStackingLayer calc_layer(ObClient *self)
               */
               (self->decorations == 0 &&
                !(self->max_horz && self->max_vert) &&
-               RECT_EQUAL(self->area, *monitor))) &&
+               (RECT_EQUAL(self->area, *monitor) ||
+                RECT_EQUAL(self->area, *allmonitors)))) &&
              /* you are fullscreen while you or your children are focused.. */
              (client_focused(self) || client_search_focus_tree(self) ||
               /* you can be fullscreen if you're on another desktop */
@@ -2421,6 +2512,7 @@ static ObStackingLayer calc_layer(ObClient *self)
     else l = OB_STACKING_LAYER_NORMAL;
 
     g_free(monitor);
+    g_free(allmonitors);
 
     return l;
 }
@@ -2528,10 +2620,6 @@ gboolean client_hide(ObClient *self)
     gboolean hide = FALSE;
 
     if (!client_should_show(self)) {
-        if (self == focus_client) {
-            event_cancel_all_key_grabs();
-        }
-
         /* We don't need to ignore enter events here.
            The window can hide/iconify in 3 different ways:
            1 - through an x message. in this case we ignore all enter events
@@ -2595,7 +2683,6 @@ gboolean client_enter_focusable(ObClient *self)
             self->type != OB_CLIENT_TYPE_DESKTOP);
 }
 
-
 static void client_apply_startup_state(ObClient *self,
                                        gint x, gint y, gint w, gint h)
 {
@@ -2796,8 +2883,10 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
     /* gets the client's position */
     frame_frame_gravity(self->frame, x, y);
 
-    /* work within the preferred sizes given by the window */
-    if (!(*w == self->area.width && *h == self->area.height)) {
+    /* work within the preferred sizes given by the window, these may have
+       changed rather than it's requested width and height, so always run
+       through this code */
+    {
         gint basew, baseh, minw, minh;
         gint incw, inch;
         gfloat minratio, maxratio;
@@ -2903,7 +2992,6 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
     g_assert(*h > 0);
 }
 
-
 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
                       gboolean user, gboolean final, gboolean force_reply)
 {
@@ -3124,7 +3212,7 @@ static void client_iconify_recursive(ObClient *self,
                 self->iconic = iconic;
 
                 /* update the focus lists.. iconic windows go to the bottom of
-                   the list */
+                   the list. this will also call focus_cycle_addremove(). */
                 focus_order_to_bottom(self);
 
                 changed = TRUE;
@@ -3136,9 +3224,10 @@ static void client_iconify_recursive(ObClient *self,
                 self->desktop != DESKTOP_ALL)
                 client_set_desktop(self, screen_desktop, FALSE, FALSE);
 
-            /* this puts it after the current focused window */
-            focus_order_remove(self);
-            focus_order_add_new(self);
+            /* this puts it after the current focused window, this will
+               also cause focus_cycle_addremove() to be called for the
+               client */
+            focus_order_like_new(self);
 
             changed = TRUE;
         }
@@ -3176,7 +3265,7 @@ void client_maximize(ObClient *self, gboolean max, gint dir)
     gint x, y, w, h;
 
     g_assert(dir == 0 || dir == 1 || dir == 2);
-    if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
+    if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && max) return;/* can't */
 
     /* check if already done */
     if (max) {
@@ -3312,12 +3401,20 @@ void client_close(ObClient *self)
 #define OB_KILL_RESULT_NO 0
 #define OB_KILL_RESULT_YES 1
 
-static void client_kill_requested(ObPrompt *p, gint result, gpointer data)
+static gboolean client_kill_requested(ObPrompt *p, gint result, gpointer data)
 {
     ObClient *self = data;
 
     if (result == OB_KILL_RESULT_YES)
         client_kill(self);
+    return TRUE; /* call the cleanup func */
+}
+
+static void client_kill_cleanup(ObPrompt *p, gpointer data)
+{
+    ObClient *self = data;
+
+    g_assert(p == self->kill_prompt);
 
     prompt_unref(self->kill_prompt);
     self->kill_prompt = NULL;
@@ -3332,7 +3429,14 @@ static void client_prompt_kill(ObClient *self)
             { 0, OB_KILL_RESULT_YES }
         };
         gchar *m;
-        const gchar *y;
+        const gchar *y, *title;
+
+        title = self->original_title;
+        if (title[0] == '\0') {
+            /* empty string, so use its parent */
+            ObClient *p = client_search_top_direct_parent(self);
+            if (p) title = p->original_title;
+        }
 
         if (client_on_localhost(self)) {
             const gchar *sig;
@@ -3344,24 +3448,26 @@ static void client_prompt_kill(ObClient *self)
 
             m = g_strdup_printf
                 (_("The window \"%s\" does not seem to be responding.  Do you want to force it to exit by sending the %s signal?"),
-                 self->original_title, sig);
+                 title, sig);
             y = _("End Process");
         }
         else {
             m = g_strdup_printf
                 (_("The window \"%s\" does not seem to be responding.  Do you want to disconnect it from the X server?"),
-                 self->original_title);
+                 title);
             y = _("Disconnect");
         }
         /* set the dialog buttons' text */
         answers[0].text = _("Cancel");  /* "no" */
         answers[1].text = y;            /* "yes" */
 
-        self->kill_prompt = prompt_new(m, answers,
+        self->kill_prompt = prompt_new(m, NULL, answers,
                                        sizeof(answers)/sizeof(answers[0]),
                                        OB_KILL_RESULT_NO, /* default = no */
                                        OB_KILL_RESULT_NO, /* cancel = no */
-                                       client_kill_requested, self);
+                                       client_kill_requested,
+                                       client_kill_cleanup,
+                                       self);
         g_free(m);
     }
 
@@ -3404,8 +3510,18 @@ void client_hilite(ObClient *self, gboolean hilite)
     /* don't allow focused windows to hilite */
     self->demands_attention = hilite && !client_focused(self);
     if (self->frame != NULL) { /* if we're mapping, just set the state */
-        if (self->demands_attention)
+        if (self->demands_attention) {
             frame_flash_start(self->frame);
+
+            /* if the window is on another desktop then raise it and make it
+               the most recently used window */
+            if (self->desktop != screen_desktop &&
+                self->desktop != DESKTOP_ALL)
+            {
+                stacking_raise(CLIENT_AS_WINDOW(self));
+                focus_order_to_top(self);
+            }
+        }
         else
             frame_flash_stop(self->frame);
         client_change_state(self);
@@ -3444,6 +3560,8 @@ static void client_set_desktop_recursive(ObClient *self,
             /* the new desktop's geometry may be different, so we may need to
                resize, for example if we are maximized */
             client_reconfigure(self, FALSE);
+
+        focus_cycle_addremove(self, FALSE);
     }
 
     /* move all transients */
@@ -3459,6 +3577,8 @@ void client_set_desktop(ObClient *self, guint target,
 {
     self = client_search_top_direct_parent(self);
     client_set_desktop_recursive(self, target, donthide, dontraise);
+
+    focus_cycle_addremove(NULL, TRUE);
 }
 
 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
@@ -3480,19 +3600,38 @@ ObClient *client_search_modal_child(ObClient *self)
     return NULL;
 }
 
+static gboolean client_validate_unmap(ObClient *self, int n)
+{
+    XEvent e;
+    gboolean ret = TRUE;
+
+    if (XCheckTypedWindowEvent(obt_display, self->window, UnmapNotify, &e)) {
+        if (n < self->ignore_unmaps) // ignore this one, but look for more
+            ret = client_validate_unmap(self, n+1);
+        else
+            ret = FALSE; // the window is going to become unmanaged
+
+        /* put them back on the event stack so they end up in the same order */
+        XPutBackEvent(obt_display, &e);
+    }
+
+    return ret;
+}
+
 gboolean client_validate(ObClient *self)
 {
     XEvent e;
 
     XSync(obt_display, FALSE); /* get all events on the server */
 
-    if (XCheckTypedWindowEvent(obt_display, self->window, DestroyNotify, &e) ||
-        XCheckTypedWindowEvent(obt_display, self->window, UnmapNotify, &e))
-    {
+    if (XCheckTypedWindowEvent(obt_display, self->window, DestroyNotify, &e)) {
         XPutBackEvent(obt_display, &e);
         return FALSE;
     }
 
+    if (!client_validate_unmap(self, 0))
+        return FALSE;
+
     return TRUE;
 }
 
@@ -3562,6 +3701,8 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
                 value = self->demands_attention;
             else if (state == OBT_PROP_ATOM(OB_WM_STATE_UNDECORATED))
                 value = undecorated;
+            else
+                g_assert_not_reached();
             action = value ? OBT_PROP_ATOM(NET_WM_STATE_REMOVE) :
                              OBT_PROP_ATOM(NET_WM_STATE_ADD);
         }
@@ -3651,6 +3792,8 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
         client_hilite(self, demands_attention);
 
     client_change_state(self); /* change the hint to reflect these changes */
+
+    focus_cycle_addremove(self, TRUE);
 }
 
 ObClient *client_focus_target(ObClient *self)
@@ -3699,8 +3842,6 @@ gboolean client_focus(ObClient *self)
        go moving on us */
     event_halt_focus_delay();
 
-    event_cancel_all_key_grabs();
-
     obt_display_ignore_errors(TRUE);
 
     if (self->can_focus) {
@@ -3758,12 +3899,20 @@ static void client_present(ObClient *self, gboolean here, gboolean raise,
     client_focus(self);
 }
 
-/* this function exists to map to the client_activate message in the ewmh,
-   the user arg is unused because nobody uses it correctly anyway. */
-void client_activate(ObClient *self, gboolean here, gboolean raise,
+/* this function exists to map to the net_active_window message in the ewmh */
+void client_activate(ObClient *self, gboolean desktop,
+                     gboolean here, gboolean raise,
                      gboolean unshade, gboolean user)
 {
-    client_present(self, here, raise, unshade);
+    if ((user && (desktop ||
+                  self->desktop == DESKTOP_ALL ||
+                  self->desktop == screen_desktop)) ||
+        client_can_steal_focus(self, event_curtime, CurrentTime))
+    {
+        client_present(self, here, raise, unshade);
+    }
+    else
+        client_hilite(self, TRUE);
 }
 
 static void client_bring_windows_recursive(ObClient *self,
@@ -3908,6 +4057,21 @@ ObClient *client_search_focus_parent(ObClient *self)
     return NULL;
 }
 
+ObClient *client_search_focus_parent_full(ObClient *self)
+{
+    GSList *it;
+    ObClient *ret = NULL;
+
+    for (it = self->parents; it; it = g_slist_next(it)) {
+        if (client_focused(it->data))
+            ret = it->data;
+        else
+            ret = client_search_focus_parent_full(it->data);
+        if (ret) break;
+    }
+    return ret;
+}
+
 ObClient *client_search_parent(ObClient *self, ObClient *search)
 {
     GSList *it;
@@ -3991,12 +4155,12 @@ static void detect_edge(Rect area, ObDirection dir,
             /* check if the head of this window is closer than the previously
                chosen edge (take into account that the previously chosen
                edge might have been a tail, not a head) */
-            if (head + (*near_edge ? 0 : my_size) < *dest)
+            if (head + (*near_edge ? 0 : my_size) <= *dest)
                 skip_head = TRUE;
             /* check if the tail of this window is closer than the previously
                chosen edge (take into account that the previously chosen
                edge might have been a head, not a tail) */
-            if (tail - (!*near_edge ? 0 : my_size) < *dest)
+            if (tail - (!*near_edge ? 0 : my_size) <= *dest)
                 skip_tail = TRUE;
             break;
         case OB_DIRECTION_SOUTH:
@@ -4010,12 +4174,12 @@ static void detect_edge(Rect area, ObDirection dir,
             /* check if the head of this window is closer than the previously
                chosen edge (take into account that the previously chosen
                edge might have been a tail, not a head) */
-            if (head - (*near_edge ? 0 : my_size) > *dest)
+            if (head - (*near_edge ? 0 : my_size) >= *dest)
                 skip_head = TRUE;
             /* check if the tail of this window is closer than the previously
                chosen edge (take into account that the previously chosen
                edge might have been a head, not a tail) */
-            if (tail + (!*near_edge ? 0 : my_size) > *dest)
+            if (tail + (!*near_edge ? 0 : my_size) >= *dest)
                 skip_tail = TRUE;
             break;
         default:
@@ -4023,7 +4187,7 @@ static void detect_edge(Rect area, ObDirection dir,
     }
 
     ob_debug("my head %d size %d", my_head, my_size);
-    ob_debug("head %d tail %d deest %d", head, tail, *dest);
+    ob_debug("head %d tail %d dest %d", head, tail, *dest);
     if (!skip_head) {
         ob_debug("using near edge %d", head);
         *dest = head;
@@ -4042,39 +4206,26 @@ void client_find_edge_directional(ObClient *self, ObDirection dir,
                                   gint *dest, gboolean *near_edge)
 {
     GList *it;
-    Rect *a, *mon;
+    Rect *a;
     Rect dock_area;
     gint edge;
+    guint i;
 
     a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS,
                     &self->frame->area);
-    mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR,
-                      &self->frame->area);
 
     switch (dir) {
     case OB_DIRECTION_NORTH:
-        if (my_head >= RECT_TOP(*mon) + 1)
-            edge = RECT_TOP(*mon) - 1;
-        else
-            edge = RECT_TOP(*a) - 1;
+        edge = RECT_TOP(*a) - 1;
         break;
     case OB_DIRECTION_SOUTH:
-        if (my_head <= RECT_BOTTOM(*mon) - 1)
-            edge = RECT_BOTTOM(*mon) + 1;
-        else
-            edge = RECT_BOTTOM(*a) + 1;
+        edge = RECT_BOTTOM(*a) + 1;
         break;
     case OB_DIRECTION_EAST:
-        if (my_head <= RECT_RIGHT(*mon) - 1)
-            edge = RECT_RIGHT(*mon) + 1;
-        else
-            edge = RECT_RIGHT(*a) + 1;
+        edge = RECT_RIGHT(*a) + 1;
         break;
     case OB_DIRECTION_WEST:
-        if (my_head >= RECT_LEFT(*mon) + 1)
-            edge = RECT_LEFT(*mon) - 1;
-        else
-            edge = RECT_LEFT(*a) - 1;
+        edge = RECT_LEFT(*a) - 1;
         break;
     default:
         g_assert_not_reached();
@@ -4083,6 +4234,15 @@ void client_find_edge_directional(ObClient *self, ObDirection dir,
     *dest = edge;
     *near_edge = TRUE;
 
+    /* search for edges of monitors */
+    for (i = 0; i < screen_num_monitors; ++i) {
+        Rect *area = screen_area(self->desktop, i, NULL);
+        detect_edge(*area, dir, my_head, my_size, my_edge_start,
+                    my_edge_size, dest, near_edge);
+        g_free(area);
+    }
+
+    /* search for edges of clients */
     for (it = client_list; it; it = g_list_next(it)) {
         ObClient *cur = it->data;
 
@@ -4104,7 +4264,6 @@ void client_find_edge_directional(ObClient *self, ObDirection dir,
     detect_edge(dock_area, dir, my_head, my_size, my_edge_start,
                 my_edge_size, dest, near_edge);
     g_free(a);
-    g_free(mon);
 }
 
 void client_find_move_directional(ObClient *self, ObDirection dir,
@@ -4186,28 +4345,28 @@ void client_find_resize_directional(ObClient *self, ObDirection side,
     switch (side) {
     case OB_DIRECTION_EAST:
         head = RECT_RIGHT(self->frame->area) +
-            (self->size_inc.width - 1) * (grow ? 1 : -1);
+            (self->size_inc.width - 1) * (grow ? 1 : 0);
         e_start = RECT_TOP(self->frame->area);
         e_size = self->frame->area.height;
         dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST;
         break;
     case OB_DIRECTION_WEST:
         head = RECT_LEFT(self->frame->area) -
-            (self->size_inc.width - 1) * (grow ? 1 : -1);
+            (self->size_inc.width - 1) * (grow ? 1 : 0);
         e_start = RECT_TOP(self->frame->area);
         e_size = self->frame->area.height;
         dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST;
         break;
     case OB_DIRECTION_NORTH:
         head = RECT_TOP(self->frame->area) -
-            (self->size_inc.height - 1) * (grow ? 1 : -1);
+            (self->size_inc.height - 1) * (grow ? 1 : 0);
         e_start = RECT_LEFT(self->frame->area);
         e_size = self->frame->area.width;
         dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH;
         break;
     case OB_DIRECTION_SOUTH:
         head = RECT_BOTTOM(self->frame->area) +
-            (self->size_inc.height - 1) * (grow ? 1 : -1);
+            (self->size_inc.height - 1) * (grow ? 1 : 0);
         e_start = RECT_LEFT(self->frame->area);
         e_size = self->frame->area.width;
         dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH;