gnome-shell/src/shell-tray-icon.c

277 lines
8.8 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "config.h"
#include "shell-tray-icon.h"
#include "shell-gtk-embed.h"
#include "tray/na-tray-child.h"
#include <gdk/gdkx.h>
Don't use ClutterX11TexturePixmap directly to embed docked windows Previously when a client requests that a window should be docked the shell would reparent the socket window onto the stage's window and then use ClutterX11TexturePixmap to get a texture to represent the window. This will not work if Clutter is no longer using the X11 winsys for example if it becomes its own display server. Instead this patch leaves the socket window as a child of the root window and lets mutter create a MetaWindow out of it. If Mutter is acting as a display server then this mechanism will still work via the headless x server. The ShellGtkEmbed instance now registers for notification of the ‘window-created’ signal of the display so that it can find the MetaWindow that gets created to represent the socket window. When this window is found it is prevented from being displayed on the screen by setting the actor's opacity to 0. An input shape is then set on the window to prevent it receiving any input. Instead of being a subclass of ClutterX11TexturePixmap, ShellGtkEmbed is now a subclass of ClutterClone. When the MetaWindow is found for the socket window the clone's source is set to the invisible actor for the window so it can be displayed in the panel as before. The ShellEmbeddedWindow no longer needs to know what the stage is because it no longer reparents the socket window. Therefore the ShellTrayManager doesn't need to know the stage either so shell_tray_manager_manage_stage has been replaced with just shell_tray_manager_manage_screen. https://bugzilla.gnome.org/show_bug.cgi?id=693438
2013-02-12 22:31:38 +00:00
#include <X11/Xatom.h>
#include "st.h"
enum {
PROP_0,
PROP_PID,
PROP_TITLE,
PROP_WM_CLASS
};
struct _ShellTrayIconPrivate
{
NaTrayChild *socket;
pid_t pid;
char *title, *wm_class;
};
G_DEFINE_TYPE_WITH_PRIVATE (ShellTrayIcon, shell_tray_icon, SHELL_TYPE_GTK_EMBED);
static void
shell_tray_icon_finalize (GObject *object)
{
ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
g_free (icon->priv->title);
g_free (icon->priv->wm_class);
G_OBJECT_CLASS (shell_tray_icon_parent_class)->finalize (object);
}
static void
shell_tray_icon_constructed (GObject *object)
{
GdkWindow *icon_app_window;
ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
ShellEmbeddedWindow *window;
GdkDisplay *display;
Window plug_xid;
Atom _NET_WM_PID, type;
int result, format;
gulong nitems, bytes_after, *val = NULL;
/* We do all this now rather than computing it on the fly later,
* because the shell may want to see their values from a
* tray-icon-removed signal handler, at which point the plug has
* already been removed from the socket.
*/
g_object_get (object, "window", &window, NULL);
g_return_if_fail (window != NULL);
icon->priv->socket = NA_TRAY_CHILD (gtk_bin_get_child (GTK_BIN (window)));
g_object_unref (window);
icon->priv->title = na_tray_child_get_title (icon->priv->socket);
na_tray_child_get_wm_class (icon->priv->socket, NULL, &icon->priv->wm_class);
icon_app_window = gtk_socket_get_plug_window (GTK_SOCKET (icon->priv->socket));
plug_xid = GDK_WINDOW_XID (icon_app_window);
display = gtk_widget_get_display (GTK_WIDGET (icon->priv->socket));
gdk_error_trap_push ();
_NET_WM_PID = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_PID");
result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), plug_xid,
_NET_WM_PID, 0, G_MAXLONG, False, XA_CARDINAL,
&type, &format, &nitems,
&bytes_after, (guchar **)&val);
if (!gdk_error_trap_pop () &&
result == Success &&
type == XA_CARDINAL &&
nitems == 1)
icon->priv->pid = *val;
if (val)
XFree (val);
}
static void
shell_tray_icon_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ShellTrayIcon *icon = SHELL_TRAY_ICON (object);
switch (prop_id)
{
case PROP_PID:
g_value_set_uint (value, icon->priv->pid);
break;
case PROP_TITLE:
g_value_set_string (value, icon->priv->title);
break;
case PROP_WM_CLASS:
g_value_set_string (value, icon->priv->wm_class);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_tray_icon_class_init (ShellTrayIconClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = shell_tray_icon_get_property;
object_class->constructed = shell_tray_icon_constructed;
object_class->finalize = shell_tray_icon_finalize;
g_object_class_install_property (object_class,
PROP_PID,
g_param_spec_uint ("pid",
"PID",
"The PID of the icon's application",
0, G_MAXUINT, 0,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_TITLE,
g_param_spec_string ("title",
"Title",
"The icon's window title",
NULL,
G_PARAM_READABLE));
g_object_class_install_property (object_class,
PROP_WM_CLASS,
g_param_spec_string ("wm-class",
"WM Class",
"The icon's window WM_CLASS",
NULL,
G_PARAM_READABLE));
}
static void
shell_tray_icon_init (ShellTrayIcon *icon)
{
icon->priv = shell_tray_icon_get_instance_private (icon);
}
/*
* Public API
*/
ClutterActor *
shell_tray_icon_new (ShellEmbeddedWindow *window)
{
g_return_val_if_fail (SHELL_IS_EMBEDDED_WINDOW (window), NULL);
return g_object_new (SHELL_TYPE_TRAY_ICON,
"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, %CLUTTER_KEY_PRESS or %CLUTTER_KEY_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)
{
XKeyEvent xkevent;
XButtonEvent xbevent;
XCrossingEvent xcevent;
GdkWindow *remote_window;
GdkScreen *screen;
int x_root, y_root;
Display *xdisplay;
Window xwindow, xrootwindow;
ClutterEventType event_type = clutter_event_type (event);
g_return_if_fail (event_type == CLUTTER_BUTTON_RELEASE ||
event_type == CLUTTER_KEY_PRESS ||
event_type == CLUTTER_KEY_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 */
if (event_type == CLUTTER_BUTTON_RELEASE)
{
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.same_screen = True;
xbevent.type = ButtonPress;
xbevent.button = clutter_event_get_button (event);
XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
xbevent.type = ButtonRelease;
XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xbevent);
}
else
{
xkevent.window = xwindow;
xkevent.root = xrootwindow;
xkevent.subwindow = None;
xkevent.time = xcevent.time;
xkevent.x = xcevent.x;
xkevent.y = xcevent.y;
xkevent.x_root = xcevent.x_root;
xkevent.y_root = xcevent.y_root;
xkevent.state = clutter_event_get_state (event);
xkevent.same_screen = True;
xkevent.keycode = clutter_event_get_key_code (event);
xkevent.type = KeyPress;
XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xkevent);
if (event_type == CLUTTER_KEY_RELEASE)
{
/* If the application takes a grab on KeyPress, we don't
* want to send it a KeyRelease. There's no good way of
* knowing whether a tray icon will take a grab, so just
* assume it does, and don't send the KeyRelease. That might
* make the tracking for key events messed up if it doesn't take
* a grab, but the tray icon won't get key focus in normal cases,
* so let's hope this isn't too damaging...
*/
xkevent.type = KeyRelease;
XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xkevent);
}
}
/* And move the pointer back out */
xcevent.type = LeaveNotify;
XSendEvent (xdisplay, xwindow, False, 0, (XEvent *)&xcevent);
gdk_error_trap_pop_ignored ();
}