mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-09 18:30:08 +00:00
x11: Implement claiming the X Selection with the clipboard
... and of course support writing to other apps.
This commit is contained in:
parent
e2014850b9
commit
134076e738
@ -367,6 +367,9 @@ gdk_fullscreen_mode_get_type
|
||||
<SECTION>
|
||||
<FILE>gdkcontentformats</FILE>
|
||||
<TITLE>Content Formats</TITLE>
|
||||
gdk_intern_mime_type
|
||||
|
||||
<SUBSECTION>
|
||||
GdkContentFormats
|
||||
gdk_content_formats_new
|
||||
gdk_content_formats_ref
|
||||
|
@ -63,7 +63,7 @@
|
||||
#include "gdkcontentformats.h"
|
||||
#include "gdkcontentformatsprivate.h"
|
||||
|
||||
#include "gdkproperty.h"
|
||||
#include <string.h>
|
||||
|
||||
struct _GdkContentFormats
|
||||
{
|
||||
@ -81,6 +81,37 @@ G_DEFINE_BOXED_TYPE (GdkContentFormats, gdk_content_formats,
|
||||
gdk_content_formats_unref)
|
||||
|
||||
|
||||
/**
|
||||
* gdk_intern_mime_type:
|
||||
* @string: (transfer none): string of a potential mime type
|
||||
*
|
||||
* Canonicalizes the given mime type and interns the result.
|
||||
*
|
||||
* If @string is not a valid mime type, %NULL is returned instead.
|
||||
* See RFC 2048 for the syntax if mime types.
|
||||
*
|
||||
* Returns: An interned string for the canonicalized mime type
|
||||
* or %NULL if the string wasn't a valid mime type
|
||||
**/
|
||||
const char *
|
||||
gdk_intern_mime_type (const char *string)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
g_return_val_if_fail (string != NULL, NULL);
|
||||
|
||||
if (!strchr (string, '/'))
|
||||
return NULL;
|
||||
|
||||
tmp = g_ascii_strdown (string, -1);
|
||||
|
||||
string = g_intern_string (tmp);
|
||||
|
||||
g_free (tmp);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
static GdkContentFormats *
|
||||
gdk_content_formats_new_take (GType * gtypes,
|
||||
gsize n_gtypes,
|
||||
|
@ -30,6 +30,9 @@ G_BEGIN_DECLS
|
||||
|
||||
#define GDK_TYPE_CONTENT_FORMATS (gdk_content_formats_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_3_94
|
||||
const char * gdk_intern_mime_type (const char *string);
|
||||
|
||||
GDK_AVAILABLE_IN_3_94
|
||||
GType gdk_content_formats_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_3_94
|
||||
|
@ -23,7 +23,9 @@
|
||||
#include "gdkintl.h"
|
||||
#include "gdkprivate-x11.h"
|
||||
#include "gdkselectioninputstream-x11.h"
|
||||
#include "gdkselectionoutputstream-x11.h"
|
||||
#include "gdktextlistconverter-x11.h"
|
||||
#include "gdk/gdk-private.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <X11/Xatom.h>
|
||||
@ -44,7 +46,7 @@ struct _GdkX11Clipboard
|
||||
|
||||
char *selection;
|
||||
Atom xselection;
|
||||
guint32 timestamp;
|
||||
gulong timestamp;
|
||||
};
|
||||
|
||||
struct _GdkX11ClipboardClass
|
||||
@ -54,6 +56,144 @@ struct _GdkX11ClipboardClass
|
||||
|
||||
G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD)
|
||||
|
||||
static void
|
||||
print_atoms (GdkX11Clipboard *cb,
|
||||
const char *prefix,
|
||||
const Atom *atoms,
|
||||
gsize n_atoms)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD,
|
||||
GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
|
||||
gsize i;
|
||||
|
||||
g_printerr ("%s: %s [ ", cb->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
|
||||
gdk_x11_clipboard_default_output_done (GObject *clipboard,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: failed to write stream: %s\n", GDK_X11_CLIPBOARD (clipboard)->selection, error->message));
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_clipboard_default_output_handler (GdkX11Clipboard *cb,
|
||||
const char *target,
|
||||
const char *encoding,
|
||||
int format,
|
||||
GOutputStream *stream)
|
||||
{
|
||||
gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
|
||||
g_intern_string (target),
|
||||
stream,
|
||||
G_PRIORITY_DEFAULT,
|
||||
NULL,
|
||||
gdk_x11_clipboard_default_output_done,
|
||||
NULL);
|
||||
g_object_unref (stream);
|
||||
}
|
||||
|
||||
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 Atom *
|
||||
gdk_x11_clipboard_formats_to_atoms (GdkDisplay *display,
|
||||
GdkContentFormats *formats,
|
||||
gsize *n_atoms);
|
||||
|
||||
static void
|
||||
handle_targets (GdkX11Clipboard *cb,
|
||||
const char *target,
|
||||
const char *encoding,
|
||||
int format,
|
||||
GOutputStream *stream)
|
||||
{
|
||||
GdkClipboard *clipboard = GDK_CLIPBOARD (cb);
|
||||
Atom *atoms;
|
||||
gsize n_atoms;
|
||||
|
||||
atoms = gdk_x11_clipboard_formats_to_atoms (gdk_clipboard_get_display (clipboard),
|
||||
gdk_clipboard_get_formats (clipboard),
|
||||
&n_atoms);
|
||||
print_atoms (cb, "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 (GdkX11Clipboard *cb,
|
||||
const char *target,
|
||||
const char *encoding,
|
||||
int format,
|
||||
GOutputStream *stream)
|
||||
{
|
||||
gulong *timestamp;
|
||||
|
||||
timestamp = g_slice_new (gulong);
|
||||
*timestamp = cb->timestamp;
|
||||
|
||||
g_output_stream_write_all_async (stream,
|
||||
timestamp,
|
||||
sizeof (gulong),
|
||||
G_PRIORITY_DEFAULT,
|
||||
NULL,
|
||||
handle_timestamp_done,
|
||||
timestamp);
|
||||
g_object_unref (stream);
|
||||
}
|
||||
|
||||
static GInputStream *
|
||||
text_list_convert (GdkX11Clipboard *cb,
|
||||
GInputStream *stream,
|
||||
@ -74,6 +214,37 @@ text_list_convert (GdkX11Clipboard *cb,
|
||||
return converter_stream;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_text_list (GdkX11Clipboard *cb,
|
||||
const char *target,
|
||||
const char *encoding,
|
||||
int format,
|
||||
GOutputStream *stream)
|
||||
{
|
||||
GOutputStream *converter_stream;
|
||||
GConverter *converter;
|
||||
|
||||
converter = gdk_x11_text_list_converter_to_utf8_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
|
||||
encoding,
|
||||
format);
|
||||
converter_stream = g_converter_output_stream_new (stream, converter);
|
||||
|
||||
g_object_unref (converter);
|
||||
g_object_unref (stream);
|
||||
|
||||
gdk_x11_clipboard_default_output_handler (cb, "text/plain;charset=utf-8", encoding, format, converter_stream);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_utf8 (GdkX11Clipboard *cb,
|
||||
const char *target,
|
||||
const char *encoding,
|
||||
int format,
|
||||
GOutputStream *stream)
|
||||
{
|
||||
gdk_x11_clipboard_default_output_handler (cb, "text/plain;charset=utf-8", encoding, format, stream);
|
||||
}
|
||||
|
||||
static GInputStream *
|
||||
no_convert (GdkX11Clipboard *cb,
|
||||
GInputStream *stream,
|
||||
@ -83,36 +254,24 @@ no_convert (GdkX11Clipboard *cb,
|
||||
return stream;
|
||||
}
|
||||
|
||||
typedef void (* MimeTypeHandleFunc) (GdkX11Clipboard *, const char *, const char *, int, GOutputStream *);
|
||||
|
||||
static const struct {
|
||||
const char *x_target;
|
||||
const char *mime_type;
|
||||
GInputStream * (* convert) (GdkX11Clipboard *, GInputStream *, const char *, int);
|
||||
const char *type;
|
||||
gint format;
|
||||
MimeTypeHandleFunc handler;
|
||||
} special_targets[] = {
|
||||
{ "UTF8_STRING", "text/plain;charset=utf-8", no_convert },
|
||||
{ "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert },
|
||||
{ "TEXT", "text/plain;charset=utf-8", text_list_convert },
|
||||
{ "STRING", "text/plain;charset=utf-8", text_list_convert }
|
||||
{ "UTF8_STRING", "text/plain;charset=utf-8", no_convert, "UTF8_STRING", 8, handle_utf8 },
|
||||
{ "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert, "COMPOUND_TEXT", 8, handle_text_list },
|
||||
{ "TEXT", "text/plain;charset=utf-8", text_list_convert, "STRING", 8, handle_text_list },
|
||||
{ "STRING", "text/plain;charset=utf-8", text_list_convert, "STRING", 8, handle_text_list },
|
||||
{ "TARGETS", NULL, NULL, "ATOM", 32, handle_targets },
|
||||
{ "TIMESTAMP", NULL, NULL, "INTEGER", 32, handle_timestamp }
|
||||
};
|
||||
|
||||
static void
|
||||
print_atoms (GdkX11Clipboard *cb,
|
||||
const char *prefix,
|
||||
const Atom *atoms,
|
||||
gsize n_atoms)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD,
|
||||
GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
|
||||
gsize i;
|
||||
|
||||
g_printerr ("%s: %s [ ", cb->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 GSList *
|
||||
gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
|
||||
{
|
||||
@ -127,8 +286,11 @@ gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
|
||||
{
|
||||
for (j = 0; j < G_N_ELEMENTS (special_targets); j++)
|
||||
{
|
||||
if (special_targets[j].mime_type == NULL)
|
||||
continue;
|
||||
|
||||
if (g_str_equal (mime_types[i], special_targets[j].mime_type))
|
||||
targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
|
||||
targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[j].x_target));
|
||||
}
|
||||
targets = g_slist_prepend (targets, (gpointer) mime_types[i]);
|
||||
}
|
||||
@ -136,6 +298,35 @@ gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
|
||||
return g_slist_reverse (targets);
|
||||
}
|
||||
|
||||
static Atom *
|
||||
gdk_x11_clipboard_formats_to_atoms (GdkDisplay *display,
|
||||
GdkContentFormats *formats,
|
||||
gsize *n_atoms)
|
||||
{
|
||||
GSList *l, *targets;
|
||||
Atom *atoms;
|
||||
gsize i;
|
||||
|
||||
targets = gdk_x11_clipboard_formats_to_targets (formats);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
|
||||
{
|
||||
if (special_targets[i].mime_type != NULL)
|
||||
continue;
|
||||
|
||||
if (special_targets[i].handler)
|
||||
targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
|
||||
}
|
||||
|
||||
*n_atoms = g_slist_length (targets);
|
||||
atoms = g_new (Atom, *n_atoms);
|
||||
i = 0;
|
||||
for (l = targets; l; l = l->next)
|
||||
atoms[i++] = gdk_x11_get_xatom_by_name_for_display (display, l->data);
|
||||
|
||||
return atoms;
|
||||
}
|
||||
|
||||
static GdkContentFormats *
|
||||
gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display,
|
||||
const Atom *atoms,
|
||||
@ -160,7 +351,8 @@ gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display,
|
||||
{
|
||||
if (g_str_equal (name, special_targets[j].x_target))
|
||||
{
|
||||
gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
|
||||
if (special_targets[j].mime_type)
|
||||
gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -184,6 +376,7 @@ gdk_x11_clipboard_request_targets_finish (GObject *source_object,
|
||||
bytes = g_input_stream_read_bytes_finish (stream, res, &error);
|
||||
if (bytes == NULL)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: error reading TARGETS: %s\n", cb->selection, error->message));
|
||||
g_error_free (error);
|
||||
g_object_unref (stream);
|
||||
g_object_unref (cb);
|
||||
@ -235,11 +428,15 @@ gdk_x11_clipboard_request_targets_got_stream (GObject *source,
|
||||
stream = gdk_x11_selection_input_stream_new_finish (result, &type, &format, &error);
|
||||
if (stream == NULL)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: can't request TARGETS: %s\n", cb->selection, error->message));
|
||||
g_object_unref (cb);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
else if (!g_str_equal (type, "ATOM") || format != 32)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: Wrong reply type to TARGETS: type %s != ATOM or format %d != 32\n",
|
||||
cb->selection, type, format));
|
||||
g_input_stream_close (stream, NULL, NULL);
|
||||
g_object_unref (stream);
|
||||
g_object_unref (cb);
|
||||
@ -269,6 +466,19 @@ gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb)
|
||||
g_object_ref (cb));
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_clipboard_claim_remote (GdkX11Clipboard *cb,
|
||||
guint32 timestamp)
|
||||
{
|
||||
GdkContentFormats *empty;
|
||||
|
||||
empty = gdk_content_formats_new (NULL, 0);
|
||||
gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), empty);
|
||||
gdk_content_formats_unref (empty);
|
||||
cb->timestamp = timestamp;
|
||||
gdk_x11_clipboard_request_targets (cb);
|
||||
}
|
||||
|
||||
static GdkFilterReturn
|
||||
gdk_x11_clipboard_filter_event (GdkXEvent *xev,
|
||||
GdkEvent *gdkevent,
|
||||
@ -287,23 +497,147 @@ gdk_x11_clipboard_filter_event (GdkXEvent *xev,
|
||||
|
||||
switch (xevent->type)
|
||||
{
|
||||
case SelectionClear:
|
||||
if (xevent->xselectionclear.selection != cb->xselection)
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
if (xevent->xselectionclear.time < cb->timestamp)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: ignoring SelectionClear with too old timestamp (%lu vs %lu)\n",
|
||||
cb->selection, xevent->xselectionclear.time, cb->timestamp));
|
||||
return GDK_FILTER_CONTINUE;
|
||||
}
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionClear\n", cb->selection));
|
||||
gdk_x11_clipboard_claim_remote (cb, xevent->xselectionclear.time);
|
||||
return GDK_FILTER_REMOVE;
|
||||
|
||||
case SelectionRequest:
|
||||
{
|
||||
GOutputStream *stream;
|
||||
const char *target, *property, *type, *mime_type;
|
||||
MimeTypeHandleFunc handler_func = NULL;
|
||||
gint format;
|
||||
gsize i;
|
||||
|
||||
if (xevent->xselectionrequest.selection != cb->xselection)
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
target = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.target);
|
||||
if (xevent->xselectionrequest.property == None)
|
||||
property = target;
|
||||
else
|
||||
property = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.property);
|
||||
|
||||
if (!gdk_clipboard_is_local (GDK_CLIPBOARD (cb)))
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s even though we don't own the selection, huh?\n",
|
||||
cb->selection, target, property));
|
||||
return GDK_FILTER_REMOVE;
|
||||
}
|
||||
if (xevent->xselectionrequest.requestor == None)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s with NULL window, ignoring\n",
|
||||
cb->selection, target, property));
|
||||
return GDK_FILTER_REMOVE;
|
||||
}
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s\n",
|
||||
cb->selection, target, property));
|
||||
mime_type = gdk_intern_mime_type (target);
|
||||
if (mime_type)
|
||||
{
|
||||
handler_func = gdk_x11_clipboard_default_output_handler;
|
||||
type = target;
|
||||
format = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
|
||||
{
|
||||
if (g_str_equal (target, special_targets[i].x_target) &&
|
||||
special_targets[i].handler)
|
||||
{
|
||||
if (special_targets[i].mime_type)
|
||||
mime_type = gdk_intern_mime_type (special_targets[i].mime_type);
|
||||
handler_func = special_targets[i].handler;
|
||||
type = special_targets[i].type;
|
||||
format = special_targets[i].format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handler_func == NULL ||
|
||||
(mime_type && !gdk_content_formats_contain_mime_type (gdk_clipboard_get_formats (GDK_CLIPBOARD (cb)), mime_type)))
|
||||
{
|
||||
Display *xdisplay = gdk_x11_display_get_xdisplay (display);
|
||||
XSelectionEvent xreply;
|
||||
int error;
|
||||
|
||||
xreply.type = SelectionNotify;
|
||||
xreply.serial = 0;
|
||||
xreply.send_event = True;
|
||||
xreply.requestor = xevent->xselectionrequest.requestor,
|
||||
xreply.selection = xevent->xselectionrequest.selection;
|
||||
xreply.target = xevent->xselectionrequest.target;
|
||||
xreply.property = None;
|
||||
xreply.time = xevent->xselectionrequest.time;
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s%s: Sending SelectionNotify rejecting request\n",
|
||||
cb->selection, target));
|
||||
|
||||
gdk_x11_display_error_trap_push (display);
|
||||
if (XSendEvent (xdisplay, xreply.requestor, False, NoEventMask, (XEvent*) & xreply) == 0)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s:%s: failed to XSendEvent()\n",
|
||||
cb->selection, target));
|
||||
g_warning ("failed to XSendEvent()");
|
||||
}
|
||||
XSync (xdisplay, False);
|
||||
|
||||
error = gdk_x11_display_error_trap_pop (display);
|
||||
if (error != Success)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s:%s: X error during write: %d\n",
|
||||
cb->selection, target, error));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = gdk_x11_selection_output_stream_new (display,
|
||||
xevent->xselectionrequest.requestor,
|
||||
cb->selection,
|
||||
target,
|
||||
property,
|
||||
type,
|
||||
format,
|
||||
xevent->xselectionrequest.time);
|
||||
handler_func (cb, target, type, format, stream);
|
||||
}
|
||||
return GDK_FILTER_REMOVE;
|
||||
}
|
||||
|
||||
default:
|
||||
#ifdef HAVE_XFIXES
|
||||
if (xevent->type - GDK_X11_DISPLAY (display)->xfixes_event_base == XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent;
|
||||
{
|
||||
XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent;
|
||||
|
||||
if (sn->selection == cb->xselection)
|
||||
if (sn->selection != cb->xselection)
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
if (sn->owner == GDK_X11_DISPLAY (display)->leader_window)
|
||||
{
|
||||
GdkContentFormats *empty;
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: got FixesSelectionNotify\n", cb->selection));
|
||||
empty = gdk_content_formats_new (NULL, 0);
|
||||
gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), empty);
|
||||
gdk_content_formats_unref (empty);
|
||||
cb->timestamp = sn->selection_timestamp;
|
||||
gdk_x11_clipboard_request_targets (cb);
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: Ignoring XFixesSelectionNotify for ourselves\n",
|
||||
cb->selection));
|
||||
return GDK_FILTER_CONTINUE;
|
||||
}
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: Received XFixesSelectionNotify, claiming selection\n",
|
||||
cb->selection));
|
||||
|
||||
gdk_x11_clipboard_claim_remote (cb, sn->selection_timestamp);
|
||||
}
|
||||
#endif
|
||||
return GDK_FILTER_CONTINUE;
|
||||
@ -321,6 +655,44 @@ gdk_x11_clipboard_finalize (GObject *object)
|
||||
G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_x11_clipboard_claim (GdkClipboard *clipboard,
|
||||
GdkContentFormats *formats,
|
||||
gboolean local,
|
||||
GdkContentProvider *content)
|
||||
{
|
||||
if (local)
|
||||
{
|
||||
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
|
||||
GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
|
||||
Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
|
||||
Window xwindow = GDK_X11_DISPLAY (display)->leader_window;
|
||||
guint32 time;
|
||||
|
||||
time = gdk_display_get_last_seen_time (display);
|
||||
|
||||
if (content)
|
||||
{
|
||||
XSetSelectionOwner (xdisplay, cb->xselection, xwindow, time);
|
||||
|
||||
if (XGetSelectionOwner (xdisplay, cb->xselection) != xwindow)
|
||||
{
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: failed XSetSelectionOwner()\n", cb->selection));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XSetSelectionOwner (xdisplay, cb->xselection, None, time);
|
||||
}
|
||||
|
||||
cb->timestamp = time;
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: claimed via XSetSelectionOwner()\n", cb->selection));
|
||||
}
|
||||
|
||||
return GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->claim (clipboard, formats, local, content);
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_clipboard_read_got_stream (GObject *source,
|
||||
GAsyncResult *res,
|
||||
@ -371,6 +743,8 @@ gdk_x11_clipboard_read_got_stream (GObject *source,
|
||||
{
|
||||
if (g_str_equal (mime_type, special_targets[i].x_target))
|
||||
{
|
||||
g_assert (special_targets[i].mime_type != NULL);
|
||||
|
||||
GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading with converter from %s to %s\n",
|
||||
cb->selection, mime_type, special_targets[i].mime_type));
|
||||
mime_type = g_intern_string (special_targets[i].mime_type);
|
||||
@ -468,6 +842,7 @@ gdk_x11_clipboard_class_init (GdkX11ClipboardClass *class)
|
||||
|
||||
object_class->finalize = gdk_x11_clipboard_finalize;
|
||||
|
||||
clipboard_class->claim = gdk_x11_clipboard_claim;
|
||||
clipboard_class->read_async = gdk_x11_clipboard_read_async;
|
||||
clipboard_class->read_finish = gdk_x11_clipboard_read_finish;
|
||||
}
|
||||
@ -493,7 +868,7 @@ gdk_x11_clipboard_new (GdkDisplay *display,
|
||||
|
||||
gdk_display_request_selection_notification (display, gdk_atom_intern (selection, FALSE));
|
||||
gdk_window_add_filter (NULL, gdk_x11_clipboard_filter_event, cb);
|
||||
gdk_x11_clipboard_request_targets (cb);
|
||||
gdk_x11_clipboard_claim_remote (cb, CurrentTime);
|
||||
|
||||
return GDK_CLIPBOARD (cb);
|
||||
}
|
||||
|
@ -2054,7 +2054,7 @@ gdk_x11_display_finalize (GObject *object)
|
||||
_gdk_x11_display_free_translate_queue (GDK_DISPLAY (display_x11));
|
||||
|
||||
/* Get rid of pending streams */
|
||||
g_slist_free_full (display_x11->input_streams, g_object_unref);
|
||||
g_slist_free_full (display_x11->streams, g_object_unref);
|
||||
|
||||
/* Atom Hashtable */
|
||||
g_hash_table_destroy (display_x11->atom_from_virtual);
|
||||
|
@ -107,7 +107,7 @@ struct _GdkX11Display
|
||||
GQueue *translate_queue;
|
||||
|
||||
/* streams reading selections */
|
||||
GSList *input_streams;
|
||||
GSList *streams;
|
||||
|
||||
/* input GdkWindow list */
|
||||
GList *input_windows;
|
||||
|
@ -160,7 +160,7 @@ gdk_x11_selection_input_stream_complete (GdkX11SelectionInputStream *stream)
|
||||
g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0));
|
||||
gdk_x11_selection_input_stream_flush (stream);
|
||||
|
||||
GDK_X11_DISPLAY (priv->display)->input_streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->input_streams, stream);
|
||||
GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, stream);
|
||||
gdk_window_remove_filter (NULL, gdk_x11_selection_input_stream_filter_event, stream);
|
||||
|
||||
g_object_unref (stream);
|
||||
@ -521,7 +521,7 @@ gdk_x11_selection_input_stream_new_async (GdkDisplay *display,
|
||||
priv = gdk_x11_selection_input_stream_get_instance_private (stream);
|
||||
|
||||
priv->display = display;
|
||||
GDK_X11_DISPLAY (display)->input_streams = g_slist_prepend (GDK_X11_DISPLAY (display)->input_streams, stream);
|
||||
GDK_X11_DISPLAY (display)->streams = g_slist_prepend (GDK_X11_DISPLAY (display)->streams, stream);
|
||||
priv->selection = g_strdup (selection);
|
||||
priv->xselection = gdk_x11_get_xatom_by_name_for_display (display, priv->selection);
|
||||
priv->target = g_strdup (target);
|
||||
|
585
gdk/x11/gdkselectionoutputstream-x11.c
Normal file
585
gdk/x11/gdkselectionoutputstream-x11.c
Normal file
@ -0,0 +1,585 @@
|
||||
/* 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 "gdkdisplay-x11.h"
|
||||
#include "gdkintl.h"
|
||||
#include "gdkx11display.h"
|
||||
#include "gdkx11property.h"
|
||||
#include "gdkx11window.h"
|
||||
|
||||
typedef struct GdkX11SelectionOutputStreamPrivate GdkX11SelectionOutputStreamPrivate;
|
||||
|
||||
struct GdkX11SelectionOutputStreamPrivate {
|
||||
GdkDisplay *display;
|
||||
Window xwindow;
|
||||
char *selection;
|
||||
Atom xselection;
|
||||
char *target;
|
||||
Atom xtarget;
|
||||
char *property;
|
||||
Atom xproperty;
|
||||
const char *type;
|
||||
Atom xtype;
|
||||
int format;
|
||||
gulong timestamp;
|
||||
|
||||
GMutex mutex;
|
||||
GCond cond;
|
||||
GByteArray *data;
|
||||
guint flush_requested : 1;
|
||||
|
||||
GTask *pending_task;
|
||||
|
||||
guint started : 1;
|
||||
guint incr : 1;
|
||||
guint delete_pending : 1;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (GdkX11SelectionOutputStream, gdk_x11_selection_output_stream, G_TYPE_OUTPUT_STREAM);
|
||||
|
||||
static GdkFilterReturn
|
||||
gdk_x11_selection_output_stream_filter_event (GdkXEvent *xevent,
|
||||
GdkEvent *gdkevent,
|
||||
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->data->len == 0)
|
||||
return FALSE;
|
||||
|
||||
if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
|
||||
return TRUE;
|
||||
|
||||
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->started && !g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
|
||||
{
|
||||
XWindowAttributes attrs;
|
||||
|
||||
priv->incr = TRUE;
|
||||
GDK_NOTE(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_NOTE(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->started)
|
||||
{
|
||||
XSelectionEvent xevent;
|
||||
|
||||
xevent.type = SelectionNotify;
|
||||
xevent.serial = 0;
|
||||
xevent.send_event = True;
|
||||
xevent.requestor = priv->xwindow;
|
||||
xevent.selection = priv->xselection;
|
||||
xevent.target = priv->xtarget;
|
||||
xevent.property = priv->xproperty;
|
||||
xevent.time = priv->timestamp;
|
||||
|
||||
if (XSendEvent (xdisplay, priv->xwindow, False, NoEventMask, (XEvent*) & xevent) == 0)
|
||||
{
|
||||
GDK_NOTE(SELECTION, g_printerr ("%s:%s: failed to XSendEvent()\n",
|
||||
priv->selection, priv->target));
|
||||
g_warning ("failed to XSendEvent()");
|
||||
}
|
||||
XSync (xdisplay, False);
|
||||
|
||||
GDK_NOTE(SELECTION, g_printerr ("%s:%s: sent SelectionNotify for %s on %s\n",
|
||||
priv->selection, priv->target, priv->target, priv->property));
|
||||
priv->started = TRUE;
|
||||
}
|
||||
|
||||
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_NOTE(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 gboolean
|
||||
gdk_x11_selection_output_stream_invoke_close (gpointer stream)
|
||||
{
|
||||
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
||||
|
||||
GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, stream);
|
||||
gdk_window_remove_filter (NULL, gdk_x11_selection_output_stream_filter_event, stream);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_x11_selection_output_stream_close (GOutputStream *stream,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_close, stream);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_x11_selection_output_stream_close_async (GOutputStream *stream,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (stream, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, gdk_x11_selection_output_stream_close_async);
|
||||
g_task_set_priority (task, io_priority);
|
||||
|
||||
gdk_x11_selection_output_stream_invoke_close (stream);
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_x11_selection_output_stream_close_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_close_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);
|
||||
|
||||
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_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->close_fn = gdk_x11_selection_output_stream_close;
|
||||
|
||||
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;
|
||||
output_stream_class->close_async = gdk_x11_selection_output_stream_close_async;
|
||||
output_stream_class->close_finish = gdk_x11_selection_output_stream_close_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 GdkFilterReturn
|
||||
gdk_x11_selection_output_stream_filter_event (GdkXEvent *xev,
|
||||
GdkEvent *gdkevent,
|
||||
gpointer data)
|
||||
{
|
||||
GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data);
|
||||
GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
|
||||
XEvent *xevent = xev;
|
||||
Display *xdisplay;
|
||||
|
||||
xdisplay = gdk_x11_display_get_xdisplay (priv->display);
|
||||
|
||||
if (xevent->xany.display != xdisplay ||
|
||||
xevent->xany.window != priv->xwindow)
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
switch (xevent->type)
|
||||
{
|
||||
case PropertyNotify:
|
||||
if (!priv->incr ||
|
||||
xevent->xproperty.atom != priv->xproperty ||
|
||||
xevent->xproperty.state != PropertyDelete)
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
GDK_NOTE(SELECTION, g_printerr ("%s:%s: got PropertyNotify Delete during INCR\n",
|
||||
priv->selection, priv->target));
|
||||
return GDK_FILTER_CONTINUE;
|
||||
|
||||
default:
|
||||
return GDK_FILTER_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
GOutputStream *
|
||||
gdk_x11_selection_output_stream_new (GdkDisplay *display,
|
||||
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->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;
|
||||
|
||||
gdk_window_add_filter (NULL, gdk_x11_selection_output_stream_filter_event, stream);
|
||||
|
||||
return G_OUTPUT_STREAM (stream);
|
||||
}
|
67
gdk/x11/gdkselectionoutputstream-x11.h
Normal file
67
gdk/x11/gdkselectionoutputstream-x11.h
Normal file
@ -0,0 +1,67 @@
|
||||
/* 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>
|
||||
*/
|
||||
|
||||
#ifndef __GDK_X11_SELECTION_OUTPUT_STREAM_H__
|
||||
#define __GDK_X11_SELECTION_OUTPUT_STREAM_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include "gdktypes.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GDK_TYPE_X11_SELECTION_OUTPUT_STREAM (gdk_x11_selection_output_stream_get_type ())
|
||||
#define GDK_X11_SELECTION_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStream))
|
||||
#define GDK_X11_SELECTION_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStreamClass))
|
||||
#define GDK_IS_X11_SELECTION_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_X11_SELECTION_OUTPUT_STREAM))
|
||||
#define GDK_IS_X11_SELECTION_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDK_TYPE_X11_SELECTION_OUTPUT_STREAM))
|
||||
#define GDK_X11_SELECTION_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStreamClass))
|
||||
|
||||
typedef struct GdkX11SelectionOutputStream GdkX11SelectionOutputStream;
|
||||
typedef struct GdkX11SelectionOutputStreamClass GdkX11SelectionOutputStreamClass;
|
||||
|
||||
struct GdkX11SelectionOutputStream
|
||||
{
|
||||
GOutputStream parent_instance;
|
||||
};
|
||||
|
||||
struct GdkX11SelectionOutputStreamClass
|
||||
{
|
||||
GOutputStreamClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
GType gdk_x11_selection_output_stream_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GOutputStream * gdk_x11_selection_output_stream_new (GdkDisplay *display,
|
||||
Window window,
|
||||
const char *selection,
|
||||
const char *target,
|
||||
const char *property,
|
||||
const char *type,
|
||||
int format,
|
||||
gulong timestamp);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GDK_X11_SELECTION_OUTPUT_STREAM_H__ */
|
@ -39,6 +39,8 @@ struct _GdkX11TextListConverter
|
||||
|
||||
const char *encoding; /* interned */
|
||||
gint format;
|
||||
|
||||
guint encoder : 1;
|
||||
};
|
||||
|
||||
struct _GdkX11TextListConverterClass
|
||||
@ -47,17 +49,39 @@ struct _GdkX11TextListConverterClass
|
||||
};
|
||||
|
||||
static GConverterResult
|
||||
gdk_x11_text_list_converter_convert (GConverter *converter,
|
||||
const void *inbuf,
|
||||
gsize inbuf_size,
|
||||
void *outbuf,
|
||||
gsize outbuf_size,
|
||||
GConverterFlags flags,
|
||||
gsize *bytes_read,
|
||||
gsize *bytes_written,
|
||||
GError **error)
|
||||
write_output (void *outbuf,
|
||||
gsize outbuf_size,
|
||||
gsize *bytes_written,
|
||||
const void *data,
|
||||
gssize len,
|
||||
GError **error)
|
||||
{
|
||||
if (len < 0)
|
||||
len = strlen (data) + 1;
|
||||
|
||||
if (outbuf_size < len)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
|
||||
_("Not enough space in destination"));
|
||||
return G_CONVERTER_ERROR;
|
||||
}
|
||||
|
||||
memcpy (outbuf, data, len);
|
||||
*bytes_written = len;
|
||||
return G_CONVERTER_FINISHED;
|
||||
}
|
||||
|
||||
static GConverterResult
|
||||
gdk_x11_text_list_converter_decode (GdkX11TextListConverter *conv,
|
||||
const void *inbuf,
|
||||
gsize inbuf_size,
|
||||
void *outbuf,
|
||||
gsize outbuf_size,
|
||||
GConverterFlags flags,
|
||||
gsize *bytes_read,
|
||||
gsize *bytes_written,
|
||||
GError **error)
|
||||
{
|
||||
GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter);
|
||||
gint count;
|
||||
char **list;
|
||||
|
||||
@ -83,32 +107,135 @@ gdk_x11_text_list_converter_convert (GConverter *converter,
|
||||
}
|
||||
else if (count == 0)
|
||||
{
|
||||
if (outbuf_size < 1)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
|
||||
_("Not enough space in destination"));
|
||||
return G_CONVERTER_ERROR;
|
||||
}
|
||||
((gchar *) outbuf)[0] = 0;
|
||||
*bytes_read = inbuf_size;
|
||||
*bytes_written = 1;
|
||||
return G_CONVERTER_FINISHED;
|
||||
return write_output (outbuf, outbuf_size, bytes_written, "", 1, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsize len = strlen (list[0]) + 1;
|
||||
|
||||
if (outbuf_size < len)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
|
||||
_("Not enough space in destination"));
|
||||
return G_CONVERTER_ERROR;
|
||||
}
|
||||
memcpy (outbuf, list[0], len);
|
||||
GConverterResult result;
|
||||
|
||||
result = write_output (outbuf, outbuf_size, bytes_written, list[0], -1, error);
|
||||
g_strfreev (list);
|
||||
*bytes_read = inbuf_size;
|
||||
*bytes_written = len;
|
||||
return G_CONVERTER_FINISHED;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static GConverterResult
|
||||
gdk_x11_text_list_converter_encode (GdkX11TextListConverter *conv,
|
||||
const void *inbuf,
|
||||
gsize inbuf_size,
|
||||
void *outbuf,
|
||||
gsize outbuf_size,
|
||||
GConverterFlags flags,
|
||||
gsize *bytes_read,
|
||||
gsize *bytes_written,
|
||||
GError **error)
|
||||
{
|
||||
if (!(flags & G_CONVERTER_INPUT_AT_END))
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
|
||||
_("Need complete input to do conversion"));
|
||||
return G_CONVERTER_ERROR;
|
||||
}
|
||||
|
||||
if (g_str_equal (conv->encoding, "STRING") ||
|
||||
g_str_equal (conv->encoding, "TEXT"))
|
||||
{
|
||||
GConverterResult result;
|
||||
gchar *tmp, *latin1;
|
||||
|
||||
tmp = g_strndup (inbuf, inbuf_size);
|
||||
latin1 = gdk_utf8_to_string_target (tmp);
|
||||
g_free (tmp);
|
||||
if (latin1)
|
||||
{
|
||||
result = write_output (outbuf, outbuf_size, bytes_written, latin1, -1, error);
|
||||
g_free (latin1);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Invalid byte sequence in conversion input"));
|
||||
result = G_CONVERTER_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (g_str_equal (conv->encoding, "COMPOUND_TEXT"))
|
||||
{
|
||||
GConverterResult result;
|
||||
guchar *text;
|
||||
GdkAtom encoding;
|
||||
gint format;
|
||||
gint new_length;
|
||||
char *tmp;
|
||||
|
||||
tmp = g_strndup (inbuf, inbuf_size);
|
||||
if (gdk_x11_display_utf8_to_compound_text (conv->display, tmp,
|
||||
&encoding, &format, &text, &new_length))
|
||||
{
|
||||
if (encoding == gdk_atom_intern (conv->encoding, FALSE) &&
|
||||
format == conv->format)
|
||||
{
|
||||
result = write_output (outbuf, outbuf_size, bytes_written, text, new_length, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Invalid formats in compound text conversion."));
|
||||
result = G_CONVERTER_ERROR;
|
||||
}
|
||||
gdk_x11_free_compound_text (text);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Invalid byte sequence in conversion input"));
|
||||
result = G_CONVERTER_ERROR;
|
||||
}
|
||||
g_free (tmp);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Unsupported encoding \"%s\""), conv->encoding);
|
||||
return G_CONVERTER_ERROR;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GConverterResult
|
||||
gdk_x11_text_list_converter_convert (GConverter *converter,
|
||||
const void *inbuf,
|
||||
gsize inbuf_size,
|
||||
void *outbuf,
|
||||
gsize outbuf_size,
|
||||
GConverterFlags flags,
|
||||
gsize *bytes_read,
|
||||
gsize *bytes_written,
|
||||
GError **error)
|
||||
{
|
||||
GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter);
|
||||
|
||||
if (conv->encoder)
|
||||
{
|
||||
return gdk_x11_text_list_converter_encode (conv,
|
||||
inbuf, inbuf_size,
|
||||
outbuf, outbuf_size,
|
||||
flags,
|
||||
bytes_read, bytes_written,
|
||||
error);
|
||||
}
|
||||
else
|
||||
{
|
||||
return gdk_x11_text_list_converter_decode (conv,
|
||||
inbuf, inbuf_size,
|
||||
outbuf, outbuf_size,
|
||||
flags,
|
||||
bytes_read, bytes_written,
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,3 +294,20 @@ gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display,
|
||||
return G_CONVERTER (conv);
|
||||
}
|
||||
|
||||
GConverter *
|
||||
gdk_x11_text_list_converter_from_utf8_new (GdkDisplay *display,
|
||||
const char *encoding,
|
||||
int format)
|
||||
{
|
||||
GdkX11TextListConverter *conv;
|
||||
|
||||
conv = g_object_new (GDK_TYPE_X11_TEXT_LIST_CONVERTER, NULL);
|
||||
|
||||
conv->display = g_object_ref (display);
|
||||
conv->encoding = g_intern_string (encoding);
|
||||
conv->format = format;
|
||||
conv->encoder = TRUE;
|
||||
|
||||
return G_CONVERTER (conv);
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,9 @@ GType gdk_x11_text_list_converter_get_type (void) G_GNUC_CO
|
||||
GConverter * gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display,
|
||||
const char *encoding,
|
||||
int format);
|
||||
GConverter * gdk_x11_text_list_converter_from_utf8_new (GdkDisplay *display,
|
||||
const char *encoding,
|
||||
int format);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -22,6 +22,7 @@ gdk_x11_sources = files([
|
||||
'gdkscreen-x11.c',
|
||||
'gdkselection-x11.c',
|
||||
'gdkselectioninputstream-x11.c',
|
||||
'gdkselectionoutputstream-x11.c',
|
||||
'gdktextlistconverter-x11.c',
|
||||
'gdkvisual-x11.c',
|
||||
'gdkvulkancontext-x11.c',
|
||||
|
Loading…
Reference in New Issue
Block a user