[gnome-shell] messageTray: forward clicks on trayicon SummaryItems to the icon



commit 7f17fcfafc21d3ac483e99213276aec38019fac7
Author: Dan Winship <danw gnome org>
Date:   Thu Jan 13 15:04:37 2011 -0500

    messageTray: forward clicks on trayicon SummaryItems to the icon
    
    If the user clicks on the title of a trayicon's SummaryItem, forward
    that click to the trayicon. Also adjust
    gnome_shell_plugin_xevent_filter() so that if the trayicon takes a
    grab as a result of this, we don't hide the message tray.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=630842

 js/ui/messageTray.js        |   28 +++++++++++-----
 js/ui/notificationDaemon.js |   21 ++++++++++--
 src/gnome-shell-plugin.c    |   32 ++++++++++++-------
 src/shell-tray-icon.c       |   73 +++++++++++++++++++++++++++++++++++++++++++
 src/shell-tray-icon.h       |    3 ++
 5 files changed, 132 insertions(+), 25 deletions(-)
---
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index ae1060a..e34d693 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -874,6 +874,14 @@ Source.prototype = {
         this.emit('destroy');
     },
 
+    // A subclass can redefine this to "steal" clicks from the
+    // summaryitem; Use Clutter.get_current_event() to get the
+    // details, return true to prevent the default handling from
+    // ocurring.
+    handleSummaryClick: function() {
+        return false;
+    },
+
     //// Protected methods ////
 
     // The subclass must call this at least once to set the summary icon.
@@ -903,6 +911,7 @@ SummaryItem.prototype = {
         this.source = source;
         this.actor = new St.Button({ style_class: 'summary-source-button',
                                      reactive: true,
+                                     button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO | St.ButtonMask.THREE,
                                      track_hover: true });
 
         this._sourceBox = new St.BoxLayout({ style_class: 'summary-source' });
@@ -1165,9 +1174,9 @@ MessageTray.prototype = {
                 this._onSummaryItemHoverChanged(summaryItem);
             }));
 
-        summaryItem.actor.connect('button-press-event', Lang.bind(this,
-            function (actor, event) {
-                this._onSummaryItemClicked(summaryItem, event);
+        summaryItem.actor.connect('clicked', Lang.bind(this,
+            function (actor, button) {
+                this._onSummaryItemClicked(summaryItem, button);
             }));
 
         source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
@@ -1404,13 +1413,14 @@ MessageTray.prototype = {
             this._expandedSummaryItem.setEllipsization(Pango.EllipsizeMode.END);
     },
 
-    _onSummaryItemClicked: function(summaryItem, event) {
-        let clickedButton = event.get_button();
-        if (!this._clickedSummaryItem ||
-            this._clickedSummaryItem != summaryItem ||
-            this._clickedSummaryItemMouseButton != clickedButton) {
+    _onSummaryItemClicked: function(summaryItem, button) {
+        if (summaryItem.source.handleSummaryClick())
+            this._unsetClickedSummaryItem();
+        else if (!this._clickedSummaryItem ||
+                 this._clickedSummaryItem != summaryItem ||
+                 this._clickedSummaryItemMouseButton != button) {
             this._clickedSummaryItem = summaryItem;
-            this._clickedSummaryItemMouseButton = clickedButton;
+            this._clickedSummaryItemMouseButton = button;
         } else {
             this._unsetClickedSummaryItem();
         }
diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js
index adfe870..c3c1e34 100644
--- a/js/ui/notificationDaemon.js
+++ b/js/ui/notificationDaemon.js
@@ -1,5 +1,6 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
+const Clutter = imports.gi.Clutter;
 const DBus = imports.dbus;
 const GLib = imports.gi.GLib;
 const Lang = imports.lang;
@@ -441,7 +442,7 @@ Source.prototype = {
             this.title = this.app.get_name();
         else
             this.useNotificationIcon = true;
-        this._isTrayIcon = false;
+        this._trayIcon = null;
     },
 
     notify: function(notification, icon) {
@@ -452,6 +453,18 @@ Source.prototype = {
         MessageTray.Source.prototype.notify.call(this, notification);
     },
 
+    handleSummaryClick: function() {
+        if (!this._trayIcon)
+            return false;
+
+        let event = Clutter.get_current_event();
+        if (event.type() != Clutter.EventType.BUTTON_RELEASE)
+            return false;
+
+        this._trayIcon.click(event);
+        return true;
+    },
+
     _setApp: function() {
         if (this.app)
             return;
@@ -466,7 +479,7 @@ Source.prototype = {
 
         // Only override the icon if we were previously using
         // notification-based icons (ie, not a trayicon) or if it was unset before
-        if (!this._isTrayIcon) {
+        if (!this._trayIcon) {
             this.useNotificationIcon = false;
             this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
         }
@@ -475,7 +488,7 @@ Source.prototype = {
     setTrayIcon: function(icon) {
         this._setSummaryIcon(icon);
         this.useNotificationIcon = false;
-        this._isTrayIcon = true;
+        this._trayIcon = icon;
     },
 
     open: function(notification) {
@@ -483,7 +496,7 @@ Source.prototype = {
     },
 
     _notificationRemoved: function() {
-        if (!this._isTrayIcon)
+        if (!this._trayIcon)
             this.destroy();
     },
 
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
index 8bf7441..b922ca3 100644
--- a/src/gnome-shell-plugin.c
+++ b/src/gnome-shell-plugin.c
@@ -321,20 +321,28 @@ gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
     }
 #endif
 
-  /* When the pointer leaves the stage to enter a child of the stage
-   * (like a notification icon), we don't want to produce Clutter leave
-   * events. But Clutter treats all leave events identically, so we
-   * need hide the detail = NotifyInferior events from it.
-   *
-   * Since Clutter doesn't see any event at all, this does mean that
-   * it won't produce an enter event on a Clutter actor that surrounds
-   * the child (unless it gets a MotionNotify before the Enter event).
-   * Other weirdness is likely also possible.
-   */
   if ((xev->xany.type == EnterNotify || xev->xany.type == LeaveNotify)
-      && xev->xcrossing.detail == NotifyInferior
       && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ())))
-    return TRUE;
+    {
+      /* If the pointer enters a child of the stage window (eg, a
+       * trayicon), we want to consider it to still be in the stage,
+       * so don't let Clutter see the event.
+       */
+      if (xev->xcrossing.detail == NotifyInferior)
+        return TRUE;
+
+      /* If the pointer is grabbed by a window it is not currently in,
+       * filter that out as well. In particular, if a trayicon grabs
+       * the pointer after a click on its label, we don't want to hide
+       * the message tray. Filtering out this event will leave Clutter
+       * out of sync, but that happens fairly often with grabs, and we
+       * can work around it. (Eg, shell_global_sync_pointer().)
+       */
+      if (xev->xcrossing.mode == NotifyGrab &&
+          (xev->xcrossing.detail == NotifyNonlinear ||
+           xev->xcrossing.detail == NotifyNonlinearVirtual))
+        return TRUE;
+    }
 
   /*
    * Pass the event to shell-global
diff --git a/src/shell-tray-icon.c b/src/shell-tray-icon.c
index 079a48e..08bffa8 100644
--- a/src/shell-tray-icon.c
+++ b/src/shell-tray-icon.c
@@ -165,3 +165,76 @@ shell_tray_icon_new (ShellEmbeddedWindow *window)
                        "window", window,
                        NULL);
 }
+
+/**
+ * shell_tray_icon_click:
+ * @icon: a #ShellTrayIcon
+ * @event: the #ClutterEvent triggering the fake click
+ *
+ * Fakes a press and release on @icon. @event must be a
+ * %CLUTTER_BUTTON_RELEASE event. Its relevant details will be passed
+ * on to the icon, but its coordinates will be ignored; the click is
+ * always made on the center of @icon.
+ */
+void
+shell_tray_icon_click (ShellTrayIcon *icon,
+                       ClutterEvent  *event)
+{
+  XButtonEvent xbevent;
+  XCrossingEvent xcevent;
+  GdkWindow *remote_window;
+  GdkScreen *screen;
+  int x_root, y_root;
+  Display *xdisplay;
+  Window xwindow, xrootwindow;
+
+  g_return_if_fail (clutter_event_type (event) == CLUTTER_BUTTON_RELEASE);
+
+  gdk_error_trap_push ();
+
+  remote_window = gtk_socket_get_plug_window (GTK_SOCKET (icon->priv->socket));
+  xwindow = GDK_WINDOW_XID (remote_window);
+  xdisplay = GDK_WINDOW_XDISPLAY (remote_window);
+  screen = gdk_window_get_screen (remote_window);
+  xrootwindow = GDK_WINDOW_XID (gdk_screen_get_root_window (screen));
+  gdk_window_get_origin (remote_window, &x_root, &y_root);
+
+  /* First make the icon believe the pointer is inside it */
+  xcevent.type = EnterNotify;
+  xcevent.window = xwindow;
+  xcevent.root = xrootwindow;
+  xcevent.subwindow = None;
+  xcevent.time = clutter_event_get_time (event);
+  xcevent.x = gdk_window_get_width (remote_window) / 2;
+  xcevent.y = gdk_window_get_height (remote_window) / 2;
+  xcevent.x_root = x_root + xcevent.x;
+  xcevent.y_root = y_root + xcevent.y;
+  xcevent.mode = NotifyNormal;
+  xcevent.detail = NotifyNonlinear;
+  xcevent.same_screen = True;
+  XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xcevent);
+
+  /* Now do the click */
+  xbevent.type = ButtonPress;
+  xbevent.window = xwindow;
+  xbevent.root = xrootwindow;
+  xbevent.subwindow = None;
+  xbevent.time = xcevent.time;
+  xbevent.x = xcevent.x;
+  xbevent.y = xcevent.y;
+  xbevent.x_root = xcevent.x_root;
+  xbevent.y_root = xcevent.y_root;
+  xbevent.state = clutter_event_get_state (event);
+  xbevent.button = clutter_event_get_button (event);
+  xbevent.same_screen = True;
+  XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
+
+  xbevent.type = ButtonRelease;
+  XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
+
+  /* And move the pointer back out */
+  xcevent.type = LeaveNotify;
+  XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xcevent);
+
+  gdk_error_trap_pop_ignored ();
+}
diff --git a/src/shell-tray-icon.h b/src/shell-tray-icon.h
index 88e2a14..565b306 100644
--- a/src/shell-tray-icon.h
+++ b/src/shell-tray-icon.h
@@ -31,4 +31,7 @@ struct _ShellTrayIconClass
 GType         shell_tray_icon_get_type (void) G_GNUC_CONST;
 ClutterActor *shell_tray_icon_new      (ShellEmbeddedWindow *window);
 
+void          shell_tray_icon_click    (ShellTrayIcon       *icon,
+                                        ClutterEvent        *event);
+
 #endif /* __SHELL_TRAY_ICON_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]