Index: src/display.c
===================================================================
RCS file: /cvs/gnome/metacity/src/display.c,v
retrieving revision 1.236
diff -p -u -r1.236 display.c
--- src/display.c	8 Oct 2004 22:34:34 -0000	1.236
+++ src/display.c	12 Oct 2004 20:47:28 -0000
@@ -74,6 +74,48 @@ typedef struct 
   guint ping_timeout_id;
 } MetaPingData;
 
+/* Types of mouse actions, used to determine when to raise, focus, etc.
+ *   NONE:      No relevant mouse click-and-drag action.  This is used
+ *              when no action exists for a click at the given location
+ *              or when dragging would have no meaning (such as with
+ *              buttons).  It is also used on ButtonRelease if there was
+ *              not "threshold" mouse motion between ButtonPress and
+ *              ButtonRelease.
+ *   MISC:      A mouse click-and-drag action not listed below.  This
+ *              can be used by a Client that only wants to provide basic
+ *              support for _NET_WM_MOUSE_ACTION.  It can also be used by
+ *              a Client for actions that don't fit into the other
+ *              categories while waiting for this list of action_types to
+ *              be officially extended.
+ *   DND:       Mouse action is or may be part of drag and drop
+ *   SELECTION: Mouse action is or may be part of selection (usually text
+ *              selection, but could include graphics or even be
+ *              exclusively graphics (think of office suites with
+ *              mixtures of graphics and text, for example))
+ *   SLIDING:   Mouse action is or may be part of moving a slider
+ *   WORKING:   Mouse action is or may be part of general working
+ *              (drawing in a photo editor, moving objects in a diagram
+ *              editor, etc.)
+ */
+typedef enum
+{
+  MOUSE_ACTION_NONE      = 0,
+  MOUSE_ACTION_MISC      = 1,
+  MOUSE_ACTION_DND       = 2,
+  MOUSE_ACTION_SELECTION = 3,
+  MOUSE_ACTION_SLIDING   = 4,
+  MOUSE_ACTION_WORKING   = 5
+} MouseActionType;
+
+typedef struct 
+{
+  MetaDisplay *display;
+  MetaWindow *window;
+  Window xwindow;
+  Time timestamp;
+  MouseActionType button_press_action;
+} MetaMouseActionData;
+
 typedef struct 
 {
   MetaDisplay *display;
@@ -95,6 +137,11 @@ static Window event_get_modified_window 
                                          XEvent         *event);
 static guint32 event_get_time           (MetaDisplay    *display,
                                          XEvent         *event);
+static void    queue_mouse_action       (MetaDisplay       *display,
+                                         MetaWindow        *window,
+                                         Time               timestamp);
+static void    process_mouse_action_message  (MetaDisplay    *display,
+                                              XEvent         *event);
 static void    process_request_frame_extents (MetaDisplay    *display,
                                               XEvent         *event);
 static void    process_pong_message     (MetaDisplay    *display,
@@ -163,6 +210,36 @@ remove_pending_pings_for_window (MetaDis
   g_slist_free (dead);
 }
 
+static void
+remove_pending_mouse_actions_for_window (MetaDisplay *display, Window xwindow)
+{
+  GSList *tmp;
+  GSList *dead;
+
+  /* could obviously be more efficient, don't care */
+  
+  /* build list to be removed */
+  dead = NULL;
+  for (tmp = display->pending_mouse_actions; tmp; tmp = tmp->next)
+    {
+      MetaMouseActionData *mouse_action_data = tmp->data;
+
+      if (mouse_action_data->xwindow == xwindow)
+        dead = g_slist_prepend (dead, mouse_action_data);
+    }
+
+  /* remove what we found */
+  for (tmp = dead; tmp; tmp = tmp->next)
+    {
+      MetaMouseActionData *mouse_action_data = tmp->data;
+
+      display->pending_mouse_actions =
+        g_slist_remove (display->pending_mouse_actions, mouse_action_data);
+      g_free (mouse_action_data);
+    }
+
+  g_slist_free (dead);
+}
 
 #ifdef HAVE_STARTUP_NOTIFICATION
 static void
@@ -289,7 +366,8 @@ meta_display_open (const char *name)
     "_NET_RESTACK_WINDOW",
     "_NET_MOVERESIZE_WINDOW",
     "_NET_DESKTOP_GEOMETRY",
-    "_NET_DESKTOP_VIEWPORT"
+    "_NET_DESKTOP_VIEWPORT",
+    "_NET_WM_MOUSE_ACTION"
   };
   Atom atoms[G_N_ELEMENTS(atom_names)];
   
@@ -327,6 +405,7 @@ meta_display_open (const char *name)
   display->server_grab_count = 0;
 
   display->pending_pings = NULL;
+  display->pending_mouse_actions = NULL;
   display->autoraise_timeout_id = 0;
   display->autoraise_window = NULL;
   display->focus_window = NULL;
@@ -444,6 +523,7 @@ meta_display_open (const char *name)
   display->atom_net_moveresize_window = atoms[88];
   display->atom_net_desktop_geometry = atoms[89];
   display->atom_net_desktop_viewport = atoms[90];
+  display->atom_net_wm_mouse_action = atoms[91];
 
   display->prop_hooks = NULL;
   meta_display_init_window_prop_hooks (display);
@@ -1596,24 +1676,36 @@ event_callback (XEvent   *event,
                    * minimize/close button.
                    */
 
-                  /* Raise on clicking the client area always or only
-                   * in click to focus mode? The debate rages.
-                   * Feel free to change TRUE to FALSE or vice versa
-                   */
-                  if (TRUE /* meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK */) 
+                  if (window->net_wm_mouse_action)
                     {
-                      meta_window_raise (window);
-                      
-                      meta_topic (META_DEBUG_FOCUS,
-                                  "Focusing %s due to unmodified button %d press (display.c)\n",
-                                  window->desc, event->xbutton.button);
-                      meta_window_focus (window, event->xbutton.time);
+                      queue_mouse_action (display, window, event->xbutton.time);
                     }
                   else
                     {
-                      meta_topic (META_DEBUG_FOCUS,
-                                  "Not raising window on click due to mouse/sloppy focus mode\n");
-                      meta_window_focus (window, event->xbutton.time);
+                      /* DEBUGGING */
+                      meta_warning ("%s does not support MOUSE_ACTION.\n\n",
+                                    window->desc);
+                      /* DEBUGGING */
+
+                      /* Raise on clicking the client area always or only
+                       * in click to focus mode? The debate rages.
+                       * Feel free to change TRUE to FALSE or vice versa
+                       */
+                      if (TRUE /* meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK */)
+                        {
+                          meta_window_raise (window);
+    
+                          meta_topic (META_DEBUG_FOCUS,
+                                      "Focusing %s due to unmodified button %d press (display.c)\n",
+                                      window->desc, event->xbutton.button);
+                          meta_window_focus (window, event->xbutton.time);
+                        }
+                      else
+                        {
+                          meta_topic (META_DEBUG_FOCUS,
+                                      "Not raising window on click due to mouse/sloppy focus mode\n");
+                          meta_window_focus (window, event->xbutton.time);
+                        }
                     }
                 }
               
@@ -2202,6 +2294,16 @@ event_callback (XEvent   *event,
                       filter_out_event = TRUE;
                     }
 		}
+	      else if (event->xclient.message_type ==
+		       display->atom_net_wm_mouse_action) 
+		{
+                  meta_verbose ("Received _NET_WM_MOUSE_ACTION message\n");
+                  
+                  process_mouse_action_message (display, event);
+
+                  /* We don't need to process this further */
+                  filter_out_event = TRUE;
+		}
             }
 
           if (event->xclient.message_type ==
@@ -2895,6 +2997,7 @@ meta_display_unregister_x_window (MetaDi
 
   /* Remove any pending pings */
   remove_pending_pings_for_window (display, xwindow);
+  remove_pending_mouse_actions_for_window (display, xwindow);
 }
 
 Cursor
@@ -3794,6 +3897,37 @@ meta_display_ping_window (MetaDisplay   
 }
 
 static void
+queue_mouse_action (MetaDisplay       *display,
+                    MetaWindow        *window,
+                    Time               timestamp)
+{
+  MetaMouseActionData *mouse_action_data;
+
+  if (timestamp == CurrentTime)
+    {
+      meta_warning ("Tried to queue a mouse action with CurrentTime!  "
+                    "Not allowed.\n");
+      return;
+    }
+
+  if (!window->net_wm_mouse_action)
+    {
+      meta_warning ("Window %s doesn't support _NET_WM_MOUSE_ACTION!\n",
+                    window->desc);
+      return;
+    }
+  
+  mouse_action_data = g_new (MetaMouseActionData, 1);
+  mouse_action_data->display = display;
+  mouse_action_data->window = window;
+  mouse_action_data->xwindow = window->xwindow;
+  mouse_action_data->timestamp = timestamp;
+  
+  display->pending_mouse_actions =
+    g_slist_prepend (display->pending_mouse_actions, mouse_action_data);
+}
+
+static void
 process_request_frame_extents (MetaDisplay    *display,
                                XEvent         *event)
 {
@@ -3920,6 +4054,193 @@ meta_display_window_has_pending_pings (M
     }
 
   return FALSE;
+}
+
+/* process the mouse action message */
+static void
+process_mouse_action_message (MetaDisplay    *display,
+                              XEvent         *event)
+{
+  GSList          *tmp;
+  Time             press_timestamp;
+  Time             release_timestamp;
+  Window           client_id;
+  gboolean         button_press_event;
+  MouseActionType  action_type;
+
+  press_timestamp    =   (Time)event->xclient.data.l[0];
+  release_timestamp  =   (Time)event->xclient.data.l[1];
+  client_id          = (Window)event->xclient.data.l[2];
+  button_press_event =    (int)event->xclient.data.l[3] ? FALSE : TRUE;
+  action_type        =    (int)event->xclient.data.l[4];
+
+  /* DEBUGGING */
+  meta_warning ("Received a mouse action:\n"
+                "  ButtonPress timestamp   : %lu\n"
+                "  ButtonRelease timestamp : %lu\n"
+                "  Client window id        : 0x%lx\n"
+                "  event_type              : %d\n"
+                "  action_type             : %d\n"
+                "\n",
+                press_timestamp,
+                release_timestamp,
+                client_id,
+                button_press_event,
+                action_type);
+  /* DEBUGGING */
+  
+  for (tmp = display->pending_mouse_actions; tmp; tmp = tmp->next)
+    {
+      MetaMouseActionData *mouse_action_data = tmp->data;
+
+      /* DEBUGGING */
+      meta_warning ("Comparing to mouse action with:\n"
+                    "  timestamp : %lu\n"
+                    "  xwindow   : 0x%lx\n"
+                    "\n",
+                    mouse_action_data->timestamp,
+                    mouse_action_data->xwindow);
+      /* DEBUGGING */
+
+      if (press_timestamp == mouse_action_data->timestamp &&
+          client_id       == mouse_action_data->xwindow)
+        {
+          gboolean no_action;
+          no_action = TRUE; // default, but will be modified below
+
+          if (button_press_event)
+            {
+              mouse_action_data->button_press_action = action_type;
+            }
+          else
+            {
+              /* It's easier to deal with an action_type for ButtonRelease
+               * that matches the action_type of the ButtonPress codewise.
+               * So we enforce that, but record whether there was no
+               * mouse activity between ButtonPress and ButtonRelease
+               */
+              no_action = (action_type == MOUSE_ACTION_NONE);
+              action_type = mouse_action_data->button_press_action;
+            }
+
+          meta_verbose ("Matching mouse action found for %lu, 0x%lx\n",
+                        mouse_action_data->timestamp, 
+                        mouse_action_data->xwindow);
+
+          /* Handle the response */
+          switch (action_type)
+            {
+              case MOUSE_ACTION_DND:
+              case MOUSE_ACTION_SELECTION:
+                if (button_press_event)
+                  {
+                    meta_warning ("Received a ButtonPress on %s that could begin"
+                                  " a DND or selection.  Waiting for"
+                                  " ButtonRelease.\n",
+                                  mouse_action_data->window->desc);
+                  }
+                else if (no_action)
+                  {
+                    meta_warning ("Raising %s due to click without drag\n",
+                                  mouse_action_data->window->desc);
+                    meta_window_focus (mouse_action_data->window,
+                                       release_timestamp);
+                    meta_window_raise (mouse_action_data->window);
+                  }
+                else
+                  {
+                    meta_warning ("Not raising %s; click accompanied by drag\n",
+                                  mouse_action_data->window->desc);
+                  }
+                break;
+              case MOUSE_ACTION_NONE:
+              case MOUSE_ACTION_MISC:
+              case MOUSE_ACTION_SLIDING:
+              case MOUSE_ACTION_WORKING:
+              default:
+                /* default case means action type is unknown; this case MUST
+                 * be treated the same as an action_type of MOUSE_ACTION_MISC
+                 */
+                if (button_press_event)
+                  {
+                    meta_warning ("Received a ButtonPress that could not begin"
+                                  " a DND or selection.  Activating %s\n",
+                                  mouse_action_data->window->desc);
+                    meta_window_focus (mouse_action_data->window,
+                                       mouse_action_data->timestamp);
+                    meta_window_raise (mouse_action_data->window);
+                  }
+                else
+                  {
+                    meta_warning ("Received a ButtonRelease that couldn't have"
+                                  " been part of a DND or selection."
+                                  "  Ignoring.\n");
+                  }
+                break;
+            }
+
+          /* Advance tmp to the next in the list since we are just going
+           * to break after possibly freeing this data, but we still want
+           * to check if there's other old queue mouse actions to clean
+           * out.
+           */
+          tmp = tmp->next;
+
+          if (!button_press_event)
+            {
+              /* Remove the mouse action from the pending list */
+              display->pending_mouse_actions =
+                g_slist_remove (display->pending_mouse_actions,
+                                mouse_action_data);
+
+              /* Free the mouse_action_data */
+              g_free (mouse_action_data);
+            }
+
+          break;
+        }
+    }
+
+  /* Clean out any old pending mouse actions */
+  Time relevant_timestamp = press_timestamp;
+  if (!button_press_event)
+    relevant_timestamp = release_timestamp;
+  while (tmp)
+    {
+      MetaMouseActionData *mouse_action_data = tmp->data;
+
+      /* Advance tmp; do it here since it may become invalid below */
+      tmp = tmp->next;
+
+      /* DEBUGGING */
+      meta_warning ("\n"
+                    "WARNING! WARNING! WARNING!\n"
+                    "Mouse action data is still queued for:\n"
+                    "  window    : %s\n"
+                    "  timestamp : %lu\n"
+                    "WARNING! WARNING! WARNING!\n",
+                    mouse_action_data->window->desc,
+                    mouse_action_data->timestamp);
+      /* DEBUGGING */
+
+      /* FIXME: Technically we should worry about timestamp wrap-around... */
+      if (relevant_timestamp > mouse_action_data->timestamp + 10000)
+        {
+          meta_warning ("An action was queued for %s with timestamp %lu, but "
+                        "we never got the appropriate follow-up messages from "
+                        "the client and it has been over 10 seconds.\n",
+                        mouse_action_data->window->desc,
+                        mouse_action_data->timestamp);
+
+          /* Remove the mouse action from the pending list */
+          display->pending_mouse_actions =
+            g_slist_remove (display->pending_mouse_actions,
+                            mouse_action_data);
+
+          /* Free the mouse_action_data */
+          g_free (mouse_action_data);
+        }
+    }
 }
 
 #define IN_TAB_CHAIN(w,t) (((t) == META_TAB_LIST_NORMAL && META_WINDOW_IN_NORMAL_TAB_CHAIN (w)) || ((t) == META_TAB_LIST_DOCKS && META_WINDOW_IN_DOCK_TAB_CHAIN (w)))
Index: src/display.h
===================================================================
RCS file: /cvs/gnome/metacity/src/display.h,v
retrieving revision 1.125
diff -p -u -r1.125 display.h
--- src/display.h	4 Oct 2004 21:09:08 -0000	1.125
+++ src/display.h	12 Oct 2004 20:47:28 -0000
@@ -182,6 +182,7 @@ struct _MetaDisplay
   Atom atom_net_moveresize_window;
   Atom atom_net_desktop_geometry;
   Atom atom_net_desktop_viewport;
+  Atom atom_net_wm_mouse_action;
 
   /* This is the actual window from focus events,
    * not the one we last set
@@ -230,6 +231,9 @@ struct _MetaDisplay
 
   /* Pings which we're waiting for a reply from */
   GSList     *pending_pings;
+
+  /* Mouse actions we're waiting for a reply from */
+  GSList     *pending_mouse_actions;
 
   /* Pending autoraise */
   guint       autoraise_timeout_id;
Index: src/window-props.c
===================================================================
RCS file: /cvs/gnome/metacity/src/window-props.c,v
retrieving revision 1.17
diff -p -u -r1.17 window-props.c
--- src/window-props.c	24 Jun 2004 15:47:05 -0000	1.17
+++ src/window-props.c	12 Oct 2004 20:47:28 -0000
@@ -746,6 +746,7 @@ reload_wm_protocols (MetaWindow    *wind
   window->take_focus = FALSE;
   window->delete_window = FALSE;
   window->net_wm_ping = FALSE;
+  window->net_wm_mouse_action = FALSE;
   
   if (value->type == META_PROP_VALUE_INVALID)    
     return;
@@ -762,6 +763,9 @@ reload_wm_protocols (MetaWindow    *wind
       else if (value->v.atom_list.atoms[i] ==
                window->display->atom_net_wm_ping)
         window->net_wm_ping = TRUE;
+      else if (value->v.atom_list.atoms[i] ==
+               window->display->atom_net_wm_mouse_action)
+        window->net_wm_mouse_action = TRUE;
       ++i;
     }
   
Index: src/window.h
===================================================================
RCS file: /cvs/gnome/metacity/src/window.h,v
retrieving revision 1.107
diff -p -u -r1.107 window.h
--- src/window.h	19 Aug 2004 02:05:24 -0000	1.107
+++ src/window.h	12 Oct 2004 20:47:28 -0000
@@ -152,6 +152,7 @@ struct _MetaWindow
   guint take_focus : 1;
   guint delete_window : 1;
   guint net_wm_ping : 1;
+  guint net_wm_mouse_action : 1;
   /* Globally active / No input */
   guint input : 1;
   
