forked from AuroraMiddleware/gtk
252b9fc39c
We finish the write to the output stream long after the stream has been closed, so we want to keep the event handler around to do just that. Instead, remove the handler on finalize.
1021 lines
38 KiB
C
1021 lines
38 KiB
C
/* GIO - GLib Output, Output and Streaming Library
|
|
*
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that 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 library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Benjamin Otte <otte@gnome.org>
|
|
* Christian Kellner <gicmo@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gdkselectionoutputstream-x11.h"
|
|
|
|
#include "gdkclipboard-x11.h"
|
|
#include "gdkdisplay-x11.h"
|
|
#include "gdkintl.h"
|
|
#include "gdktextlistconverter-x11.h"
|
|
#include "gdkx11display.h"
|
|
#include "gdkx11property.h"
|
|
#include "gdkx11surface.h"
|
|
|
|
typedef struct _GdkX11PendingSelectionNotify GdkX11PendingSelectionNotify;
|
|
typedef struct _GdkX11SelectionOutputStreamPrivate GdkX11SelectionOutputStreamPrivate;
|
|
|
|
struct _GdkX11SelectionOutputStreamPrivate {
|
|
GdkDisplay *display;
|
|
GdkX11PendingSelectionNotify *notify;
|
|
Window xwindow;
|
|
char *selection;
|
|
Atom xselection;
|
|
char *target;
|
|
Atom xtarget;
|
|
char *property;
|
|
Atom xproperty;
|
|
char *type;
|
|
Atom xtype;
|
|
int format;
|
|
gulong timestamp;
|
|
|
|
GMutex mutex;
|
|
GCond cond;
|
|
GByteArray *data;
|
|
guint flush_requested : 1;
|
|
|
|
GTask *pending_task;
|
|
|
|
guint incr : 1;
|
|
guint sent_end_of_stream : 1;
|
|
guint delete_pending : 1; /* owns a reference */
|
|
};
|
|
|
|
struct _GdkX11PendingSelectionNotify
|
|
{
|
|
gsize n_pending;
|
|
|
|
XSelectionEvent xevent;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GdkX11SelectionOutputStream, gdk_x11_selection_output_stream, G_TYPE_OUTPUT_STREAM);
|
|
|
|
static GdkX11PendingSelectionNotify *
|
|
gdk_x11_pending_selection_notify_new (Window window,
|
|
Atom selection,
|
|
Atom target,
|
|
Atom property,
|
|
Time timestamp)
|
|
{
|
|
GdkX11PendingSelectionNotify *pending;
|
|
|
|
pending = g_slice_new0 (GdkX11PendingSelectionNotify);
|
|
pending->n_pending = 1;
|
|
|
|
pending->xevent.type = SelectionNotify;
|
|
pending->xevent.serial = 0;
|
|
pending->xevent.send_event = True;
|
|
pending->xevent.requestor = window;
|
|
pending->xevent.selection = selection;
|
|
pending->xevent.target = target;
|
|
pending->xevent.property = property;
|
|
pending->xevent.time = timestamp;
|
|
|
|
return pending;
|
|
}
|
|
|
|
static void
|
|
gdk_x11_pending_selection_notify_free (GdkX11PendingSelectionNotify *notify)
|
|
{
|
|
g_slice_free (GdkX11PendingSelectionNotify, notify);
|
|
}
|
|
|
|
static void
|
|
gdk_x11_pending_selection_notify_require (GdkX11PendingSelectionNotify *notify,
|
|
guint n_sends)
|
|
{
|
|
notify->n_pending += n_sends;
|
|
}
|
|
|
|
static void
|
|
gdk_x11_pending_selection_notify_send (GdkX11PendingSelectionNotify *notify,
|
|
GdkDisplay *display,
|
|
gboolean success)
|
|
{
|
|
Display *xdisplay;
|
|
int error;
|
|
|
|
notify->n_pending--;
|
|
if (notify->n_pending)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: not sending SelectionNotify yet, %zu streams still pending\n",
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection),
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.target),
|
|
notify->n_pending));
|
|
return;
|
|
}
|
|
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: sending SelectionNotify reporting %s\n",
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection),
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.target),
|
|
success ? "success" : "failure"));
|
|
if (!success)
|
|
notify->xevent.property = None;
|
|
|
|
xdisplay = gdk_x11_display_get_xdisplay (display);
|
|
|
|
gdk_x11_display_error_trap_push (display);
|
|
|
|
if (XSendEvent (xdisplay, notify->xevent.requestor, False, NoEventMask, (XEvent*) ¬ify->xevent) == 0)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: failed to XSendEvent()\n",
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection),
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.target)));
|
|
}
|
|
XSync (xdisplay, False);
|
|
|
|
error = gdk_x11_display_error_trap_pop (display);
|
|
if (error != Success)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: X error during write: %d\n",
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.selection),
|
|
gdk_x11_get_xatom_name_for_display (display, notify->xevent.target),
|
|
error));
|
|
}
|
|
|
|
gdk_x11_pending_selection_notify_free (notify);
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_xevent (GdkDisplay *display,
|
|
const XEvent *xevent,
|
|
gpointer data);
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_can_flush (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
if (priv->delete_pending)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_needs_flush_unlocked (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
if (priv->sent_end_of_stream)
|
|
return FALSE;
|
|
|
|
if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)) ||
|
|
g_output_stream_is_closed (G_OUTPUT_STREAM (stream)))
|
|
return TRUE;
|
|
|
|
if (priv->data->len == 0 && priv->notify == NULL)
|
|
return FALSE;
|
|
|
|
if (priv->flush_requested)
|
|
return TRUE;
|
|
|
|
return priv->data->len >= gdk_x11_display_get_max_request_size (priv->display);
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_needs_flush (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
gboolean result;
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
|
|
result = gdk_x11_selection_output_stream_needs_flush_unlocked (stream);
|
|
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gsize
|
|
get_element_size (int format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case 8:
|
|
return 1;
|
|
|
|
case 16:
|
|
return sizeof (short);
|
|
|
|
case 32:
|
|
return sizeof (long);
|
|
|
|
default:
|
|
g_warning ("Unknown format %u", format);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_perform_flush (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
Display *xdisplay;
|
|
gsize element_size, n_elements;
|
|
int error;
|
|
|
|
g_assert (!priv->delete_pending);
|
|
|
|
xdisplay = gdk_x11_display_get_xdisplay (priv->display);
|
|
|
|
/* We operate on a foreign window, better guard against catastrophe */
|
|
gdk_x11_display_error_trap_push (priv->display);
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
|
|
element_size = get_element_size (priv->format);
|
|
n_elements = priv->data->len / element_size;
|
|
|
|
if (priv->notify && !g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
|
|
{
|
|
XWindowAttributes attrs;
|
|
|
|
priv->incr = TRUE;
|
|
GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: initiating INCR transfer\n",
|
|
priv->selection, priv->target));
|
|
|
|
XGetWindowAttributes (xdisplay,
|
|
priv->xwindow,
|
|
&attrs);
|
|
if (!(attrs.your_event_mask & PropertyChangeMask))
|
|
{
|
|
XSelectInput (xdisplay, priv->xwindow, attrs.your_event_mask | PropertyChangeMask);
|
|
}
|
|
|
|
XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display),
|
|
priv->xwindow,
|
|
priv->xproperty,
|
|
gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR"),
|
|
32,
|
|
PropModeReplace,
|
|
(guchar *) &(long) { n_elements },
|
|
1);
|
|
}
|
|
else
|
|
{
|
|
XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display),
|
|
priv->xwindow,
|
|
priv->xproperty,
|
|
priv->xtype,
|
|
priv->format,
|
|
PropModeReplace,
|
|
priv->data->data,
|
|
n_elements);
|
|
GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: wrote %zu/%u bytes\n",
|
|
priv->selection, priv->target, n_elements * element_size, priv->data->len));
|
|
g_byte_array_remove_range (priv->data, 0, n_elements * element_size);
|
|
if (priv->data->len < element_size)
|
|
priv->flush_requested = FALSE;
|
|
if (!priv->incr || n_elements == 0)
|
|
priv->sent_end_of_stream = TRUE;
|
|
}
|
|
|
|
if (priv->notify)
|
|
{
|
|
gdk_x11_pending_selection_notify_send (priv->notify, priv->display, TRUE);
|
|
priv->notify = NULL;
|
|
}
|
|
|
|
g_object_ref (stream);
|
|
priv->delete_pending = TRUE;
|
|
g_cond_broadcast (&priv->cond);
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
/* XXX: handle failure here and report EPIPE for future operations on the stream? */
|
|
error = gdk_x11_display_error_trap_pop (priv->display);
|
|
if (error != Success)
|
|
{
|
|
GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: X error during write: %d\n",
|
|
priv->selection, priv->target, error));
|
|
}
|
|
|
|
if (priv->pending_task)
|
|
{
|
|
g_task_return_int (priv->pending_task, GPOINTER_TO_SIZE (g_task_get_task_data (priv->pending_task)));
|
|
g_object_unref (priv->pending_task);
|
|
priv->pending_task = NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_invoke_flush (gpointer data)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data);
|
|
|
|
if (gdk_x11_selection_output_stream_needs_flush (stream) &&
|
|
gdk_x11_selection_output_stream_can_flush (stream))
|
|
gdk_x11_selection_output_stream_perform_flush (stream);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gssize
|
|
gdk_x11_selection_output_stream_write (GOutputStream *output_stream,
|
|
const void *buffer,
|
|
gsize count,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
g_byte_array_append (priv->data, buffer, count);
|
|
GDK_NOTE (SELECTION, g_printerr ("%s:%s: wrote %zu bytes, %u total now\n",
|
|
priv->selection, priv->target, count, priv->data->len));
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_flush, stream);
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream))
|
|
g_cond_wait (&priv->cond, &priv->mutex);
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_write_async (GOutputStream *output_stream,
|
|
const void *buffer,
|
|
gsize count,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
GTask *task;
|
|
|
|
task = g_task_new (stream, cancellable, callback, user_data);
|
|
g_task_set_source_tag (task, gdk_x11_selection_output_stream_write_async);
|
|
g_task_set_priority (task, io_priority);
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
g_byte_array_append (priv->data, buffer, count);
|
|
GDK_NOTE (SELECTION, g_printerr ("%s:%s: async wrote %zu bytes, %u total now\n",
|
|
priv->selection, priv->target, count, priv->data->len));
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
if (!gdk_x11_selection_output_stream_needs_flush (stream))
|
|
{
|
|
g_task_return_int (task, count);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
else if (!gdk_x11_selection_output_stream_can_flush (stream))
|
|
{
|
|
g_assert (priv->pending_task == NULL);
|
|
priv->pending_task = task;
|
|
g_task_set_task_data (task, GSIZE_TO_POINTER (count), NULL);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
gdk_x11_selection_output_stream_perform_flush (stream);
|
|
g_task_return_int (task, count);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gssize
|
|
gdk_x11_selection_output_stream_write_finish (GOutputStream *stream,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, stream), -1);
|
|
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_x11_selection_output_stream_write_async, -1);
|
|
|
|
return g_task_propagate_int (G_TASK (result), error);
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_request_flush (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
gboolean needs_flush;
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
|
|
if (priv->data->len >= get_element_size (priv->format))
|
|
priv->flush_requested = TRUE;
|
|
|
|
needs_flush = gdk_x11_selection_output_stream_needs_flush_unlocked (stream);
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
GDK_NOTE (SELECTION, g_printerr ("%s:%s: requested flush%s\n",
|
|
priv->selection, priv->target, needs_flush ?"" : ", but not needed"));
|
|
return needs_flush;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_flush (GOutputStream *output_stream,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
if (!gdk_x11_selection_output_request_flush (stream))
|
|
return TRUE;
|
|
|
|
g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_flush, stream);
|
|
|
|
g_mutex_lock (&priv->mutex);
|
|
if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream))
|
|
g_cond_wait (&priv->cond, &priv->mutex);
|
|
g_mutex_unlock (&priv->mutex);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_flush_async (GOutputStream *output_stream,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
GTask *task;
|
|
|
|
task = g_task_new (stream, cancellable, callback, user_data);
|
|
g_task_set_source_tag (task, gdk_x11_selection_output_stream_flush_async);
|
|
g_task_set_priority (task, io_priority);
|
|
|
|
if (!gdk_x11_selection_output_stream_can_flush (stream))
|
|
{
|
|
if (gdk_x11_selection_output_request_flush (stream))
|
|
{
|
|
g_assert (priv->pending_task == NULL);
|
|
priv->pending_task = task;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
gdk_x11_selection_output_stream_perform_flush (stream);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_flush_finish (GOutputStream *stream,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
|
|
g_return_val_if_fail (g_async_result_is_tagged (result, gdk_x11_selection_output_stream_flush_async), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_finalize (GObject *object)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (object);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
/* not sending a notify is terrible */
|
|
g_assert (priv->notify == NULL);
|
|
|
|
GDK_DISPLAY_NOTE (priv->display, SELECTION, g_printerr ("%s:%s: finalizing\n",
|
|
priv->selection, priv->target));
|
|
GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, stream);
|
|
g_signal_handlers_disconnect_by_func (priv->display,
|
|
gdk_x11_selection_output_stream_xevent,
|
|
stream);
|
|
|
|
g_byte_array_unref (priv->data);
|
|
g_cond_clear (&priv->cond);
|
|
g_mutex_clear (&priv->mutex);
|
|
|
|
g_free (priv->selection);
|
|
g_free (priv->target);
|
|
g_free (priv->property);
|
|
g_free (priv->type);
|
|
|
|
G_OBJECT_CLASS (gdk_x11_selection_output_stream_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_class_init (GdkX11SelectionOutputStreamClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass);
|
|
|
|
object_class->finalize = gdk_x11_selection_output_stream_finalize;
|
|
|
|
output_stream_class->write_fn = gdk_x11_selection_output_stream_write;
|
|
output_stream_class->flush = gdk_x11_selection_output_stream_flush;
|
|
|
|
output_stream_class->write_async = gdk_x11_selection_output_stream_write_async;
|
|
output_stream_class->write_finish = gdk_x11_selection_output_stream_write_finish;
|
|
output_stream_class->flush_async = gdk_x11_selection_output_stream_flush_async;
|
|
output_stream_class->flush_finish = gdk_x11_selection_output_stream_flush_finish;
|
|
}
|
|
|
|
static void
|
|
gdk_x11_selection_output_stream_init (GdkX11SelectionOutputStream *stream)
|
|
{
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
g_mutex_init (&priv->mutex);
|
|
g_cond_init (&priv->cond);
|
|
priv->data = g_byte_array_new ();
|
|
}
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_stream_xevent (GdkDisplay *display,
|
|
const XEvent *xevent,
|
|
gpointer data)
|
|
{
|
|
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data);
|
|
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
Display *xdisplay;
|
|
|
|
xdisplay = gdk_x11_display_get_xdisplay (priv->display);
|
|
|
|
if (xevent->xany.display != xdisplay ||
|
|
xevent->xany.window != priv->xwindow)
|
|
return FALSE;
|
|
|
|
switch (xevent->type)
|
|
{
|
|
case PropertyNotify:
|
|
if (!priv->incr ||
|
|
xevent->xproperty.atom != priv->xproperty ||
|
|
xevent->xproperty.state != PropertyDelete)
|
|
return FALSE;
|
|
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s:%s: got PropertyNotify Delete during INCR\n",
|
|
priv->selection, priv->target));
|
|
priv->delete_pending = FALSE;
|
|
if (gdk_x11_selection_output_stream_needs_flush (stream) &&
|
|
gdk_x11_selection_output_stream_can_flush (stream))
|
|
gdk_x11_selection_output_stream_perform_flush (stream);
|
|
g_object_unref (stream); /* from unsetting the delete_pending */
|
|
return FALSE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GOutputStream *
|
|
gdk_x11_selection_output_stream_new (GdkDisplay *display,
|
|
GdkX11PendingSelectionNotify *notify,
|
|
Window window,
|
|
const char *selection,
|
|
const char *target,
|
|
const char *property,
|
|
const char *type,
|
|
int format,
|
|
gulong timestamp)
|
|
{
|
|
GdkX11SelectionOutputStream *stream;
|
|
GdkX11SelectionOutputStreamPrivate *priv;
|
|
|
|
stream = g_object_new (GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, NULL);
|
|
priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
|
|
|
priv->display = display;
|
|
GDK_X11_DISPLAY (display)->streams = g_slist_prepend (GDK_X11_DISPLAY (display)->streams, stream);
|
|
priv->notify = notify;
|
|
priv->xwindow = window;
|
|
priv->selection = g_strdup (selection);
|
|
priv->xselection = gdk_x11_get_xatom_by_name_for_display (display, priv->selection);
|
|
priv->target = g_strdup (target);
|
|
priv->xtarget = gdk_x11_get_xatom_by_name_for_display (display, priv->target);
|
|
priv->property = g_strdup (property);
|
|
priv->xproperty = gdk_x11_get_xatom_by_name_for_display (display, priv->property);
|
|
priv->type = g_strdup (type);
|
|
priv->xtype = gdk_x11_get_xatom_by_name_for_display (display, priv->type);
|
|
priv->format = format;
|
|
priv->timestamp = timestamp;
|
|
|
|
g_signal_connect (display,
|
|
"xevent",
|
|
G_CALLBACK (gdk_x11_selection_output_stream_xevent),
|
|
stream);
|
|
|
|
return G_OUTPUT_STREAM (stream);
|
|
}
|
|
|
|
static void
|
|
print_atoms (GdkDisplay *display,
|
|
const char *selection,
|
|
const char *prefix,
|
|
const Atom *atoms,
|
|
gsize n_atoms)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, CLIPBOARD,
|
|
gsize i;
|
|
|
|
g_printerr ("%s: %s [ ", selection, prefix);
|
|
for (i = 0; i < n_atoms; i++)
|
|
{
|
|
g_printerr ("%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , atoms[i]));
|
|
}
|
|
g_printerr (" ]\n");
|
|
);
|
|
}
|
|
|
|
static void
|
|
handle_targets_done (GObject *stream,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
gsize bytes_written;
|
|
|
|
if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, &bytes_written, &error))
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("---: failed to send targets after %zu bytes: %s\n",
|
|
bytes_written, error->message));
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (user_data);
|
|
}
|
|
|
|
static void
|
|
handle_targets (GOutputStream *stream,
|
|
GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
const char *target,
|
|
const char *encoding,
|
|
int format,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
Atom *atoms;
|
|
gsize n_atoms;
|
|
|
|
atoms = gdk_x11_clipboard_formats_to_atoms (display,
|
|
TRUE,
|
|
formats,
|
|
&n_atoms);
|
|
print_atoms (display, "---", "sending targets", atoms, n_atoms);
|
|
g_output_stream_write_all_async (stream,
|
|
atoms,
|
|
n_atoms * sizeof (Atom),
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
handle_targets_done,
|
|
atoms);
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
static void
|
|
handle_timestamp_done (GObject *stream,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
gsize bytes_written;
|
|
|
|
if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, &bytes_written, &error))
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("---: failed to send timestamp after %zu bytes: %s\n",
|
|
bytes_written, error->message));
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_slice_free (gulong, user_data);
|
|
}
|
|
|
|
static void
|
|
handle_timestamp (GOutputStream *stream,
|
|
GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
const char *target,
|
|
const char *encoding,
|
|
int format,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
gulong *time_;
|
|
|
|
time_ = g_slice_new (gulong);
|
|
*time_ = timestamp;
|
|
|
|
g_output_stream_write_all_async (stream,
|
|
time_,
|
|
sizeof (gulong),
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
handle_timestamp_done,
|
|
time_);
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
static void
|
|
handle_save_targets (GOutputStream *stream,
|
|
GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
const char *target,
|
|
const char *encoding,
|
|
int format,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
/* Don't do anything */
|
|
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
static void
|
|
handle_text_list (GOutputStream *stream,
|
|
GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
const char *target,
|
|
const char *encoding,
|
|
int format,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
GOutputStream *converter_stream;
|
|
GConverter *converter;
|
|
|
|
converter = gdk_x11_text_list_converter_to_utf8_new (display,
|
|
encoding,
|
|
format);
|
|
converter_stream = g_converter_output_stream_new (stream, converter);
|
|
|
|
g_object_unref (converter);
|
|
g_object_unref (stream);
|
|
|
|
handler (converter_stream, gdk_intern_mime_type ("text/plain;charset=utf-8"), user_data);
|
|
}
|
|
|
|
static void
|
|
handle_utf8 (GOutputStream *stream,
|
|
GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
const char *target,
|
|
const char *encoding,
|
|
int format,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
handler (stream, gdk_intern_mime_type ("text/plain;charset=utf-8"), user_data);
|
|
}
|
|
|
|
typedef void (* MimeTypeHandleFunc) (GOutputStream *, GdkDisplay *, GdkContentFormats *, const char *, const char *, int, gulong, GdkX11SelectionOutputHandler, gpointer);
|
|
|
|
static const struct {
|
|
const char *x_target;
|
|
const char *mime_type;
|
|
const char *type;
|
|
int format;
|
|
MimeTypeHandleFunc handler;
|
|
} special_targets[] = {
|
|
{ "UTF8_STRING", "text/plain;charset=utf-8", "UTF8_STRING", 8, handle_utf8 },
|
|
{ "COMPOUND_TEXT", "text/plain;charset=utf-8", "COMPOUND_TEXT", 8, handle_text_list },
|
|
{ "TEXT", "text/plain;charset=utf-8", "STRING", 8, handle_text_list },
|
|
{ "STRING", "text/plain;charset=utf-8", "STRING", 8, handle_text_list },
|
|
{ "TARGETS", NULL, "ATOM", 32, handle_targets },
|
|
{ "TIMESTAMP", NULL, "INTEGER", 32, handle_timestamp },
|
|
{ "SAVE_TARGETS", NULL, "NULL", 32, handle_save_targets }
|
|
};
|
|
|
|
static gboolean
|
|
gdk_x11_selection_output_streams_request (GdkDisplay *display,
|
|
GdkX11PendingSelectionNotify *notify,
|
|
GdkContentFormats *formats,
|
|
Window requestor,
|
|
Atom xselection,
|
|
Atom xtarget,
|
|
Atom xproperty,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
const char *mime_type, *selection, *target, *property;
|
|
|
|
selection = gdk_x11_get_xatom_name_for_display (display, xselection);
|
|
target = gdk_x11_get_xatom_name_for_display (display, xtarget);
|
|
property = gdk_x11_get_xatom_name_for_display (display, xproperty);
|
|
mime_type = gdk_intern_mime_type (target);
|
|
|
|
if (mime_type)
|
|
{
|
|
if (gdk_content_formats_contain_mime_type (formats, mime_type))
|
|
{
|
|
GOutputStream *stream;
|
|
|
|
stream = gdk_x11_selection_output_stream_new (display,
|
|
notify,
|
|
requestor,
|
|
selection,
|
|
target,
|
|
property,
|
|
target,
|
|
8,
|
|
timestamp);
|
|
handler (stream, mime_type, user_data);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if (g_str_equal (target, "MULTIPLE"))
|
|
{
|
|
gulong n_atoms;
|
|
gulong nbytes;
|
|
Atom prop_type;
|
|
int prop_format;
|
|
Atom *atoms = NULL;
|
|
int error;
|
|
|
|
error = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
|
|
requestor,
|
|
xproperty,
|
|
0, 0x1FFFFFFF, False,
|
|
AnyPropertyType,
|
|
&prop_type, &prop_format,
|
|
&n_atoms, &nbytes, (guchar **) &atoms);
|
|
if (error != Success)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: XGetProperty() during MULTIPLE failed with %d\n",
|
|
selection, error));
|
|
}
|
|
else if (prop_format != 32 ||
|
|
prop_type != gdk_x11_get_xatom_by_name_for_display (display, "ATOM_PAIR"))
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: XGetProperty() type/format should be ATOM_PAIR/32 but is %s/%d\n",
|
|
selection, gdk_x11_get_xatom_name_for_display (display, prop_type), prop_format));
|
|
}
|
|
else if (n_atoms < 2)
|
|
{
|
|
print_atoms (display, selection, "ignoring MULTIPLE request with too little elements", atoms, n_atoms);
|
|
}
|
|
else
|
|
{
|
|
gulong i;
|
|
|
|
print_atoms (display, selection, "MULTIPLE request", atoms, n_atoms);
|
|
if (n_atoms % 2)
|
|
{
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: Number of atoms is uneven at %lu, ignoring last element\n",
|
|
selection, n_atoms));
|
|
n_atoms &= ~1;
|
|
}
|
|
|
|
gdk_x11_pending_selection_notify_require (notify, n_atoms / 2);
|
|
|
|
for (i = 0; i < n_atoms / 2; i++)
|
|
{
|
|
gboolean success;
|
|
|
|
if (atoms[2 * i] == None || atoms[2 * i + 1] == None)
|
|
{
|
|
success = FALSE;
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: None not allowed as atom in MULTIPLE request\n",
|
|
selection));
|
|
gdk_x11_pending_selection_notify_send (notify, display, FALSE);
|
|
}
|
|
else if (atoms[2 * i] == gdk_x11_get_xatom_by_name_for_display (display, "MULTIPLE"))
|
|
{
|
|
success = FALSE;
|
|
GDK_DISPLAY_NOTE (display, SELECTION, g_printerr ("%s: MULTIPLE as target in MULTIPLE request would cause recursion\n",
|
|
selection));
|
|
gdk_x11_pending_selection_notify_send (notify, display, FALSE);
|
|
}
|
|
else
|
|
{
|
|
success = gdk_x11_selection_output_streams_request (display,
|
|
notify,
|
|
formats,
|
|
requestor,
|
|
xselection,
|
|
atoms[2 * i],
|
|
atoms[2 * i + 1],
|
|
timestamp,
|
|
handler,
|
|
user_data);
|
|
}
|
|
|
|
if (!success)
|
|
atoms[2 * i + 1] = None;
|
|
}
|
|
}
|
|
|
|
XChangeProperty (gdk_x11_display_get_xdisplay (display),
|
|
requestor,
|
|
xproperty,
|
|
prop_type, 32,
|
|
PropModeReplace, (guchar *)atoms, n_atoms);
|
|
|
|
if (atoms)
|
|
XFree (atoms);
|
|
|
|
gdk_x11_pending_selection_notify_send (notify, display, TRUE);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
gsize i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
|
|
{
|
|
if (g_str_equal (target, special_targets[i].x_target) &&
|
|
special_targets[i].handler)
|
|
{
|
|
GOutputStream *stream;
|
|
|
|
if (special_targets[i].mime_type)
|
|
mime_type = gdk_intern_mime_type (special_targets[i].mime_type);
|
|
stream = gdk_x11_selection_output_stream_new (display,
|
|
notify,
|
|
requestor,
|
|
selection,
|
|
target,
|
|
property,
|
|
special_targets[i].type,
|
|
special_targets[i].format,
|
|
timestamp);
|
|
special_targets[i].handler (stream,
|
|
display,
|
|
formats,
|
|
target,
|
|
special_targets[i].type,
|
|
special_targets[i].format,
|
|
timestamp,
|
|
handler,
|
|
user_data);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
gdk_x11_pending_selection_notify_send (notify, display, FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
gdk_x11_selection_output_streams_create (GdkDisplay *display,
|
|
GdkContentFormats *formats,
|
|
Window requestor,
|
|
Atom selection,
|
|
Atom target,
|
|
Atom property,
|
|
gulong timestamp,
|
|
GdkX11SelectionOutputHandler handler,
|
|
gpointer user_data)
|
|
{
|
|
GdkX11PendingSelectionNotify *notify;
|
|
|
|
notify = gdk_x11_pending_selection_notify_new (requestor,
|
|
selection,
|
|
target,
|
|
property,
|
|
timestamp);
|
|
gdk_x11_selection_output_streams_request (display,
|
|
notify,
|
|
formats,
|
|
requestor,
|
|
selection,
|
|
target,
|
|
property,
|
|
timestamp,
|
|
handler,
|
|
user_data);
|
|
}
|