From 134076e7387abafa1599853edad38ddd703075d3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 25 Nov 2017 21:59:39 +0100 Subject: [PATCH] x11: Implement claiming the X Selection with the clipboard ... and of course support writing to other apps. --- docs/reference/gdk/gdk4-sections.txt | 3 + gdk/gdkcontentformats.c | 33 +- gdk/gdkcontentformats.h | 3 + gdk/x11/gdkclipboard-x11.c | 451 +++++++++++++++++-- gdk/x11/gdkdisplay-x11.c | 2 +- gdk/x11/gdkdisplay-x11.h | 2 +- gdk/x11/gdkselectioninputstream-x11.c | 4 +- gdk/x11/gdkselectionoutputstream-x11.c | 585 +++++++++++++++++++++++++ gdk/x11/gdkselectionoutputstream-x11.h | 67 +++ gdk/x11/gdktextlistconverter-x11.c | 204 +++++++-- gdk/x11/gdktextlistconverter-x11.h | 3 + gdk/x11/meson.build | 1 + 12 files changed, 1285 insertions(+), 73 deletions(-) create mode 100644 gdk/x11/gdkselectionoutputstream-x11.c create mode 100644 gdk/x11/gdkselectionoutputstream-x11.h diff --git a/docs/reference/gdk/gdk4-sections.txt b/docs/reference/gdk/gdk4-sections.txt index 8143c7d335..8deed84aac 100644 --- a/docs/reference/gdk/gdk4-sections.txt +++ b/docs/reference/gdk/gdk4-sections.txt @@ -367,6 +367,9 @@ gdk_fullscreen_mode_get_type
gdkcontentformats Content Formats +gdk_intern_mime_type + + GdkContentFormats gdk_content_formats_new gdk_content_formats_ref diff --git a/gdk/gdkcontentformats.c b/gdk/gdkcontentformats.c index 7b4a101e13..8369cdd6ee 100644 --- a/gdk/gdkcontentformats.c +++ b/gdk/gdkcontentformats.c @@ -63,7 +63,7 @@ #include "gdkcontentformats.h" #include "gdkcontentformatsprivate.h" -#include "gdkproperty.h" +#include 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, diff --git a/gdk/gdkcontentformats.h b/gdk/gdkcontentformats.h index 1c67d005f3..e265b6ab88 100644 --- a/gdk/gdkcontentformats.h +++ b/gdk/gdkcontentformats.h @@ -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 diff --git a/gdk/x11/gdkclipboard-x11.c b/gdk/x11/gdkclipboard-x11.c index 90c17bbfe5..c865fa9308 100644 --- a/gdk/x11/gdkclipboard-x11.c +++ b/gdk/x11/gdkclipboard-x11.c @@ -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 #include @@ -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); } diff --git a/gdk/x11/gdkdisplay-x11.c b/gdk/x11/gdkdisplay-x11.c index 30f168061a..bc63583fb7 100644 --- a/gdk/x11/gdkdisplay-x11.c +++ b/gdk/x11/gdkdisplay-x11.c @@ -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); diff --git a/gdk/x11/gdkdisplay-x11.h b/gdk/x11/gdkdisplay-x11.h index dc1100ee68..6da7cf634a 100644 --- a/gdk/x11/gdkdisplay-x11.h +++ b/gdk/x11/gdkdisplay-x11.h @@ -107,7 +107,7 @@ struct _GdkX11Display GQueue *translate_queue; /* streams reading selections */ - GSList *input_streams; + GSList *streams; /* input GdkWindow list */ GList *input_windows; diff --git a/gdk/x11/gdkselectioninputstream-x11.c b/gdk/x11/gdkselectioninputstream-x11.c index 9ba704d6d0..8fa57d1ec8 100644 --- a/gdk/x11/gdkselectioninputstream-x11.c +++ b/gdk/x11/gdkselectioninputstream-x11.c @@ -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); diff --git a/gdk/x11/gdkselectionoutputstream-x11.c b/gdk/x11/gdkselectionoutputstream-x11.c new file mode 100644 index 0000000000..00e60513d5 --- /dev/null +++ b/gdk/x11/gdkselectionoutputstream-x11.c @@ -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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#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); +} diff --git a/gdk/x11/gdkselectionoutputstream-x11.h b/gdk/x11/gdkselectionoutputstream-x11.h new file mode 100644 index 0000000000..874d35b849 --- /dev/null +++ b/gdk/x11/gdkselectionoutputstream-x11.h @@ -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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#ifndef __GDK_X11_SELECTION_OUTPUT_STREAM_H__ +#define __GDK_X11_SELECTION_OUTPUT_STREAM_H__ + +#include +#include "gdktypes.h" + +#include + +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__ */ diff --git a/gdk/x11/gdktextlistconverter-x11.c b/gdk/x11/gdktextlistconverter-x11.c index 7fe56d1734..f39dc6c157 100644 --- a/gdk/x11/gdktextlistconverter-x11.c +++ b/gdk/x11/gdktextlistconverter-x11.c @@ -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); +} + diff --git a/gdk/x11/gdktextlistconverter-x11.h b/gdk/x11/gdktextlistconverter-x11.h index 9017a8b7e1..473c518eb5 100644 --- a/gdk/x11/gdktextlistconverter-x11.h +++ b/gdk/x11/gdktextlistconverter-x11.h @@ -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 diff --git a/gdk/x11/meson.build b/gdk/x11/meson.build index db74295d8a..53213818fb 100644 --- a/gdk/x11/meson.build +++ b/gdk/x11/meson.build @@ -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',