gnome-shell/src/shell-tray-icon.c
Olivier Fourdan 0b6c5b4620 systray: Bail out if the plug window is gone
Instead of crashing when deferencing a NULL pointer.

Bugzilla: https://bugzilla.gnome.org/show_bug.cgi?id=755313
2016-09-08 12:08:02 +02:00

292 lines
9.1 KiB
C

/* -*- 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>
#include <X11/Xatom.h>
#include "st.h"
enum {
PROP_0,
PROP_PID,
PROP_TITLE,
PROP_WM_CLASS
};
typedef struct _ShellTrayIconPrivate ShellTrayIconPrivate;
struct _ShellTrayIcon
{
ShellGtkEmbed parent;
ShellTrayIconPrivate *priv;
};
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));
if (remote_window == NULL)
{
g_warning ("shell tray: plug window is gone");
gdk_error_trap_pop_ignored ();
return;
}
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 ();
}