new least-intrusive focus stealing prevention
authorDana Jansens <danakj@orodu.net>
Fri, 3 Aug 2007 22:06:10 +0000 (18:06 -0400)
committerDana Jansens <danakj@orodu.net>
Fri, 3 Aug 2007 22:11:27 +0000 (18:11 -0400)
openbox/client.c
openbox/event.c
openbox/event.h
openbox/prop.c
openbox/prop.h
openbox/screen.c

index 1931d87..cc72183 100644 (file)
@@ -214,7 +214,7 @@ void client_manage(Window window)
     ObAppSettings *settings;
     gboolean transient = FALSE;
     Rect place, *monitor;
-    Time map_time;
+    Time launch_time, map_time;
 
     grab_server(TRUE);
 
@@ -253,6 +253,8 @@ void client_manage(Window window)
 
     ob_debug("Managing window: 0x%lx\n", window);
 
+    map_time = event_get_server_time();
+
     /* choose the events we want to receive on the CLIENT window */
     attrib_set.event_mask = CLIENT_EVENTMASK;
     attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
@@ -300,7 +302,7 @@ void client_manage(Window window)
     client_setup_decor_and_functions(self, FALSE);
 
     /* tell startup notification that this app started */
-    map_time = sn_app_started(self->startup_id, self->class);
+    launch_time = sn_app_started(self->startup_id, self->class);
 
     /* do this after we have a frame.. it uses the frame to help determine the
        WM_STATE to apply. */
@@ -451,6 +453,13 @@ void client_manage(Window window)
     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)\n",
+                      self->window, map_time, launch_time,
+                      event_last_user_time);
+
         if (menu_frame_visible || moveresize_in_progress) {
             activate = FALSE;
             raise = TRUE;
@@ -464,8 +473,8 @@ void client_manage(Window window)
         else if (!(self->desktop == screen_desktop ||
               self->desktop == DESKTOP_ALL) &&
             /* the timestamp is from before you changed desktops */
-            map_time && screen_desktop_user_time &&
-            !event_time_after(map_time, screen_desktop_user_time))
+            launch_time && screen_desktop_user_time &&
+            !event_time_after(launch_time, screen_desktop_user_time))
         {
             activate = FALSE;
             raise = TRUE;
@@ -477,11 +486,21 @@ void client_manage(Window window)
         else if (focus_client && client_search_focus_tree_full(self) == NULL &&
                  client_search_focus_group_full(self) == NULL)
         {
-            /* If its a transient (and parents aren't focused) and the time
-               is ambiguous (either the current focus target doesn't have
-               a timestamp, or they are the same (we probably inherited it
-               from them) */
-            if (client_has_parent(self)) {
+            /* 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\n");
+            }
+            /* If its 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 "
@@ -510,6 +529,10 @@ void client_manage(Window window)
         }
 
         if (!activate) {
+            ob_debug_type(OB_DEBUG_FOCUS,
+                          "Focus stealing prevention activated for %s at "
+                          "time %u (last user interactioon time %u)\n",
+                          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);
index 5d01a0f..62778bc 100644 (file)
@@ -97,8 +97,8 @@ static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
 static gboolean focus_delay_func(gpointer data);
 static void focus_delay_client_dest(ObClient *client, gpointer data);
 
-/* The time for the current event being processed */
 Time event_curtime = CurrentTime;
+Time event_last_user_time = CurrentTime;
 
 static gboolean focus_left_screen = FALSE;
 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
@@ -236,6 +236,12 @@ static void event_set_curtime(XEvent *e)
         break;
     }
 
+    /* watch that if we get an event earlier than the last specified user_time,
+       which can happen if the clock goes backwards, we erase the last
+       specified user_time */
+    if (t && event_last_user_time && event_time_after(event_last_user_time, t))
+        event_last_user_time = CurrentTime;
+
     event_curtime = t;
 }
 
@@ -1496,6 +1502,16 @@ static void event_handle_client(ObClient *client, XEvent *e)
         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
             client_update_icon_geometry(client);
         }
+        else if (msgtype == prop_atoms.net_wm_user_time) {
+            guint32 t;
+            if (PROP_GET32(client->window, net_wm_user_time, cardinal, &t) &&
+                t && !event_time_after(t, e->xproperty.time) &&
+                (!event_last_user_time ||
+                 event_time_after(t, event_last_user_time)))
+            {
+                event_last_user_time = t;
+            }
+        }
 #ifdef SYNC
         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
             client_update_sync_request_counter(client);
@@ -1934,3 +1950,15 @@ gboolean event_time_after(Time t1, Time t2)
         /* t2 is in the first half so t1 has to come after it */
         return t1 >= t2 && t1 < (t2 + TIME_HALF);
 }
+
+Time event_get_server_time()
+{
+    /* Generate a timestamp */
+    XEvent event;
+
+    XChangeProperty(ob_display, screen_support_win,
+                    prop_atoms.wm_class, prop_atoms.string,
+                    8, PropModeAppend, NULL, 0);
+    XWindowEvent(ob_display, screen_support_win, PropertyChangeMask, &event);
+    return event.xproperty.time;
+}
index 5c5290f..a8d1aea 100644 (file)
 
 struct _ObClient;
 
+/*! The amount of time before a window appears that is checked for user input
+  to determine if the user is working in another window */
+#define OB_EVENT_USER_TIME_DELAY (500) /* 0.5 seconds */
+
 /*! Time at which the last event with a timestamp occured. */
 extern Time event_curtime;
+/*! The last user-interaction time, as given by the clients */
+extern Time event_last_user_time;
 
 /*! The value of the mask for the NumLock modifier */
 extern guint NumLockMask;
@@ -55,4 +61,6 @@ void event_halt_focus_delay();
   comes at the same time or later than t2. */
 gboolean event_time_after(Time t1, Time t2);
 
+Time event_get_server_time();
+
 #endif
index ed094d9..40ae6ef 100644 (file)
@@ -93,7 +93,7 @@ void prop_startup()
     CREATE(net_wm_icon_geometry, "_NET_WM_ICON_GEOMETRY");
 /*   CREATE(net_wm_pid, "_NET_WM_PID"); */
     CREATE(net_wm_allowed_actions, "_NET_WM_ALLOWED_ACTIONS");
-/*    CREATE(net_wm_user_time, "_NET_WM_USER_TIME"); */
+    CREATE(net_wm_user_time, "_NET_WM_USER_TIME");
 /*    CREATE(net_wm_user_time_window, "_NET_WM_USER_TIME_WINDOW"); */
     CREATE(kde_net_wm_frame_strut, "_KDE_NET_WM_FRAME_STRUT");
     CREATE(net_frame_extents, "_NET_FRAME_EXTENTS");
index 9aff28c..f0c4f5e 100644 (file)
@@ -131,7 +131,7 @@ typedef struct Atoms {
     Atom net_wm_icon_geometry;
 /*  Atom net_wm_pid; */
     Atom net_wm_allowed_actions;
-/*  Atom net_wm_user_time; */
+    Atom net_wm_user_time;
 /*  Atom net_wm_user_time_window; */
     Atom net_frame_extents;
 
index 8bab662..79008e7 100644 (file)
@@ -107,22 +107,7 @@ static gboolean replace_wm()
             current_wm_sn_owner = None;
     }
 
-    {
-        /* Generate a timestamp */
-        XEvent event;
-
-        XSelectInput(ob_display, screen_support_win, PropertyChangeMask);
-
-        XChangeProperty(ob_display, screen_support_win,
-                        prop_atoms.wm_class, prop_atoms.string,
-                        8, PropModeAppend, NULL, 0);
-        XWindowEvent(ob_display, screen_support_win,
-                     PropertyChangeMask, &event);
-
-        XSelectInput(ob_display, screen_support_win, NoEventMask);
-
-        timestamp = event.xproperty.time;
-    }
+    timestamp = event_get_server_time();
 
     XSetSelectionOwner(ob_display, wm_sn_atom, screen_support_win,
                        timestamp);
@@ -172,12 +157,14 @@ gboolean screen_annex()
 
     /* create the netwm support window */
     attrib.override_redirect = TRUE;
+    attrib.event_mask = PropertyChangeMask;
     screen_support_win = XCreateWindow(ob_display,
                                        RootWindow(ob_display, ob_screen),
                                        -100, -100, 1, 1, 0,
                                        CopyFromParent, InputOutput,
                                        CopyFromParent,
-                                       CWOverrideRedirect, &attrib);
+                                       CWEventMask | CWOverrideRedirect,
+                                       &attrib);
     XMapWindow(ob_display, screen_support_win);
     XLowerWindow(ob_display, screen_support_win);
 
@@ -282,8 +269,8 @@ gboolean screen_annex()
     supported[i++] = prop_atoms.net_wm_state_demands_attention;
     supported[i++] = prop_atoms.net_moveresize_window;
     supported[i++] = prop_atoms.net_wm_moveresize;
-/*
     supported[i++] = prop_atoms.net_wm_user_time;
+/*
     supported[i++] = prop_atoms.net_wm_user_time_window;
 */
     supported[i++] = prop_atoms.net_frame_extents;