gnome-shell/src/st/st-clipboard.c
Carlos Garnacho a22e9ce9cd st: Ignore filtered selection events not meant to our clipboard window
Other windows like the mutter Xwayland selection bridges might deal with
the clipboard, which would result in events visible on st-clipboard event
filters.

In order to avoid unintended results, ignore events that are not meant for
the clipboard window.

https://bugzilla.gnome.org/show_bug.cgi?id=760745
2016-10-09 10:57:47 +02:00

415 lines
11 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-clipboard.c: clipboard object
*
* Copyright 2009 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:st-clipboard
* @short_description: a simple representation of the X clipboard
*
* #StCliboard is a very simple object representation of the clipboard
* available to applications. Text is always assumed to be UTF-8 and non-text
* items are not handled.
*/
#include "st-clipboard.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <string.h>
struct _StClipboardPrivate
{
Window clipboard_window;
gchar *clipboard_text;
Atom *supported_targets;
gint n_targets;
};
G_DEFINE_TYPE_WITH_PRIVATE (StClipboard, st_clipboard, G_TYPE_OBJECT)
typedef struct _EventFilterData EventFilterData;
struct _EventFilterData
{
StClipboard *clipboard;
StClipboardCallbackFunc callback;
gpointer user_data;
};
static Atom __atom_primary = None;
static Atom __atom_clip = None;
static Atom __utf8_string = None;
static Atom __atom_targets = None;
static void
st_clipboard_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
st_clipboard_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
st_clipboard_dispose (GObject *object)
{
G_OBJECT_CLASS (st_clipboard_parent_class)->dispose (object);
}
static void
st_clipboard_finalize (GObject *object)
{
StClipboardPrivate *priv = ((StClipboard *) object)->priv;
g_free (priv->clipboard_text);
priv->clipboard_text = NULL;
g_free (priv->supported_targets);
priv->supported_targets = NULL;
priv->n_targets = 0;
G_OBJECT_CLASS (st_clipboard_parent_class)->finalize (object);
}
static GdkFilterReturn
st_clipboard_provider (GdkXEvent *xevent_p,
GdkEvent *gev,
void *user_data)
{
StClipboard *clipboard = user_data;
XEvent *xev = (XEvent *) xevent_p;
XSelectionEvent notify_event;
XSelectionRequestEvent *req_event;
GdkDisplay *display = gdk_display_get_default ();
if (xev->type != SelectionRequest ||
xev->xany.window != clipboard->priv->clipboard_window ||
!clipboard->priv->clipboard_text)
return GDK_FILTER_CONTINUE;
req_event = &xev->xselectionrequest;
gdk_x11_display_error_trap_push (display);
if (req_event->target == __atom_targets)
{
XChangeProperty (req_event->display,
req_event->requestor,
req_event->property,
XA_ATOM,
32,
PropModeReplace,
(guchar*) clipboard->priv->supported_targets,
clipboard->priv->n_targets);
}
else
{
XChangeProperty (req_event->display,
req_event->requestor,
req_event->property,
req_event->target,
8,
PropModeReplace,
(guchar*) clipboard->priv->clipboard_text,
strlen (clipboard->priv->clipboard_text));
}
notify_event.type = SelectionNotify;
notify_event.display = req_event->display;
notify_event.requestor = req_event->requestor;
notify_event.selection = req_event->selection;
notify_event.target = req_event->target;
notify_event.time = req_event->time;
if (req_event->property == None)
notify_event.property = req_event->target;
else
notify_event.property = req_event->property;
/* notify the requestor that they have a copy of the selection */
XSendEvent (req_event->display, req_event->requestor, False, 0,
(XEvent *) &notify_event);
/* Make it happen non async */
XSync (GDK_DISPLAY_XDISPLAY (display), FALSE);
if (gdk_x11_display_error_trap_pop (display))
{
/* FIXME: Warn here on fail ? */
}
return GDK_FILTER_REMOVE;
}
static void
st_clipboard_class_init (StClipboardClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = st_clipboard_get_property;
object_class->set_property = st_clipboard_set_property;
object_class->dispose = st_clipboard_dispose;
object_class->finalize = st_clipboard_finalize;
}
static void
st_clipboard_init (StClipboard *self)
{
GdkDisplay *gdk_display;
Display *dpy;
StClipboardPrivate *priv;
priv = self->priv = st_clipboard_get_instance_private (self);
gdk_display = gdk_display_get_default ();
dpy = GDK_DISPLAY_XDISPLAY (gdk_display);
priv->clipboard_window =
XCreateSimpleWindow (dpy,
gdk_x11_get_default_root_xwindow (),
-1, -1, 1, 1, 0, 0, 0);
/* Only create once */
if (__atom_primary == None)
__atom_primary = XInternAtom (dpy, "PRIMARY", 0);
if (__atom_clip == None)
__atom_clip = XInternAtom (dpy, "CLIPBOARD", 0);
if (__utf8_string == None)
__utf8_string = XInternAtom (dpy, "UTF8_STRING", 0);
if (__atom_targets == None)
__atom_targets = XInternAtom (dpy, "TARGETS", 0);
priv->n_targets = 2;
priv->supported_targets = g_new (Atom, priv->n_targets);
priv->supported_targets[0] = __utf8_string;
priv->supported_targets[1] = __atom_targets;
gdk_window_add_filter (NULL, /* all windows */
st_clipboard_provider,
self);
}
static GdkFilterReturn
st_clipboard_x11_event_filter (GdkXEvent *xevent_p,
GdkEvent *gev,
void *user_data)
{
XEvent *xev = (XEvent *) xevent_p;
EventFilterData *filter_data = user_data;
StClipboardPrivate *priv = filter_data->clipboard->priv;
Atom actual_type;
int actual_format, result;
unsigned long nitems, bytes_after;
unsigned char *data = NULL;
GdkDisplay *display = gdk_display_get_default ();
if(xev->type != SelectionNotify ||
xev->xany.window != priv->clipboard_window)
return GDK_FILTER_CONTINUE;
if (xev->xselection.property == None)
{
/* clipboard empty */
filter_data->callback (filter_data->clipboard,
NULL,
filter_data->user_data);
gdk_window_remove_filter (NULL,
st_clipboard_x11_event_filter,
filter_data);
g_free (filter_data);
return GDK_FILTER_REMOVE;
}
gdk_x11_display_error_trap_push (display);
result = XGetWindowProperty (xev->xselection.display,
xev->xselection.requestor,
xev->xselection.property,
0L, G_MAXINT,
True,
AnyPropertyType,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&data);
if (gdk_x11_display_error_trap_pop (display) || result != Success)
{
/* FIXME: handle failure better */
g_warning ("Clipboard: prop retrival failed");
}
filter_data->callback (filter_data->clipboard, (char*) data,
filter_data->user_data);
gdk_window_remove_filter (NULL,
st_clipboard_x11_event_filter,
filter_data);
g_free (filter_data);
if (data)
XFree (data);
return GDK_FILTER_REMOVE;
}
/**
* st_clipboard_get_default:
*
* Get the global #StClipboard object that represents the clipboard.
*
* Returns: (transfer none): a #StClipboard owned by St and must not be
* unrefferenced or freed.
*/
StClipboard*
st_clipboard_get_default (void)
{
static StClipboard *default_clipboard = NULL;
if (!default_clipboard)
{
default_clipboard = g_object_new (ST_TYPE_CLIPBOARD, NULL);
}
return default_clipboard;
}
static Atom
atom_for_clipboard_type (StClipboardType type)
{
return type == ST_CLIPBOARD_TYPE_CLIPBOARD ? __atom_clip : __atom_primary;
}
/**
* st_clipboard_get_text:
* @clipboard: A #StCliboard
* @type: The type of clipboard data you want
* @callback: (scope async): function to be called when the text is retreived
* @user_data: data to be passed to the callback
*
* Request the data from the clipboard in text form. @callback is executed
* when the data is retreived.
*
*/
void
st_clipboard_get_text (StClipboard *clipboard,
StClipboardType type,
StClipboardCallbackFunc callback,
gpointer user_data)
{
EventFilterData *data;
GdkDisplay *gdk_display;
Display *dpy;
g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
g_return_if_fail (callback != NULL);
data = g_new0 (EventFilterData, 1);
data->clipboard = clipboard;
data->callback = callback;
data->user_data = user_data;
gdk_window_add_filter (NULL, /* all windows */
st_clipboard_x11_event_filter,
data);
gdk_display = gdk_display_get_default ();
dpy = GDK_DISPLAY_XDISPLAY (gdk_display);
gdk_x11_display_error_trap_push (gdk_display);
XConvertSelection (dpy,
atom_for_clipboard_type (type),
__utf8_string, __utf8_string,
clipboard->priv->clipboard_window,
CurrentTime);
if (gdk_x11_display_error_trap_pop (gdk_display))
{
/* FIXME */
}
}
/**
* st_clipboard_set_text:
* @clipboard: A #StClipboard
* @type: The type of clipboard that you want to set
* @text: text to copy to the clipboard
*
* Sets text as the current contents of the clipboard.
*/
void
st_clipboard_set_text (StClipboard *clipboard,
StClipboardType type,
const gchar *text)
{
StClipboardPrivate *priv;
GdkDisplay *gdk_display;
Display *dpy;
g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
g_return_if_fail (text != NULL);
priv = clipboard->priv;
/* make a copy of the text */
g_free (priv->clipboard_text);
priv->clipboard_text = g_strdup (text);
/* tell X we own the clipboard selection */
gdk_display = gdk_display_get_default ();
dpy = GDK_DISPLAY_XDISPLAY (gdk_display);
gdk_x11_display_error_trap_push (gdk_display);
XSetSelectionOwner (dpy, atom_for_clipboard_type (type), priv->clipboard_window, CurrentTime);
XSync (dpy, FALSE);
if (gdk_x11_display_error_trap_pop (gdk_display))
{
/* FIXME */
}
}