gtk2/gdk/x11/gdkselectionoutputstream-x11.c
Benjamin Otte 252b9fc39c x11: Don't delete important signal handlers randomly
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.
2021-12-13 01:54:21 +01:00

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*) &notify->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);
}