/* GTK - The GIMP Toolkit * Copyright (C) 2017 Benjamin Otte * * 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 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 . */ #include "config.h" #include #include "gdkcontentserializer.h" #include "gdkcontentformats.h" #include "gdkpixbuf.h" #include "filetransferportalprivate.h" #include "gdktextureprivate.h" #include "gdkrgba.h" #include #include /** * GdkContentSerializer: * * A `GdkContentSerializer` is used to serialize content for * inter-application data transfers. * * The `GdkContentSerializer` transforms an object that is identified * by a GType into a serialized form (i.e. a byte stream) that is * identified by a mime type. * * GTK provides serializers and deserializers for common data types * such as text, colors, images or file lists. To register your own * serialization functions, use [func@Gdk.content_register_serializer]. * * Also see [class@Gdk.ContentDeserializer]. */ typedef struct _Serializer Serializer; struct _Serializer { const char * mime_type; /* interned */ GType type; GdkContentSerializeFunc serialize; gpointer data; GDestroyNotify notify; }; GQueue serializers = G_QUEUE_INIT; static void init (void); #define GDK_CONTENT_SERIALIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_CONTENT_SERIALIZER, GdkContentSerializerClass)) #define GDK_IS_CONTENT_SERIALIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_CONTENT_SERIALIZER)) #define GDK_CONTENT_SERIALIZER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_CONTENT_SERIALIZER, GdkContentSerializerClass)) typedef struct _GdkContentSerializerClass GdkContentSerializerClass; struct _GdkContentSerializer { GObject parent_instance; const char *mime_type; /* interned */ GValue value; GOutputStream *stream; int priority; gboolean returned; GCancellable *cancellable; gpointer user_data; GAsyncReadyCallback callback; gpointer callback_data; gpointer task_data; GDestroyNotify task_notify; GError *error; }; struct _GdkContentSerializerClass { GObjectClass parent_class; }; static gpointer gdk_content_serializer_async_result_get_user_data (GAsyncResult *res) { return GDK_CONTENT_SERIALIZER (res)->callback_data; } static GObject * gdk_content_serializer_async_result_get_source_object (GAsyncResult *res) { return NULL; } static void gdk_content_serializer_async_result_iface_init (GAsyncResultIface *iface) { iface->get_user_data = gdk_content_serializer_async_result_get_user_data; iface->get_source_object = gdk_content_serializer_async_result_get_source_object; } G_DEFINE_TYPE_WITH_CODE (GdkContentSerializer, gdk_content_serializer, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, gdk_content_serializer_async_result_iface_init)) static void gdk_content_serializer_finalize (GObject *object) { GdkContentSerializer *serializer = GDK_CONTENT_SERIALIZER (object); g_value_unset (&serializer->value); g_clear_object (&serializer->stream); g_clear_object (&serializer->cancellable); g_clear_error (&serializer->error); if (serializer->task_notify) serializer->task_notify (serializer->task_data); G_OBJECT_CLASS (gdk_content_serializer_parent_class)->finalize (object); } static void gdk_content_serializer_class_init (GdkContentSerializerClass *content_serializer_class) { GObjectClass *object_class = G_OBJECT_CLASS (content_serializer_class); object_class->finalize = gdk_content_serializer_finalize; } static void gdk_content_serializer_init (GdkContentSerializer *content_serializer) { } static void gdk_content_serializer_run (const char *mime_type, const GValue *value, GOutputStream *stream, int priority, GCancellable *cancellable, GdkContentSerializeFunc serialize_func, gpointer user_data, GAsyncReadyCallback callback, gpointer callback_data) { GdkContentSerializer *serializer; serializer = g_object_new (GDK_TYPE_CONTENT_SERIALIZER, NULL); serializer->mime_type = mime_type; g_value_init (&serializer->value, G_VALUE_TYPE (value)); g_value_copy (value, &serializer->value); serializer->stream = g_object_ref (stream); serializer->priority = priority; if (cancellable) serializer->cancellable = g_object_ref (cancellable); serializer->user_data = user_data; serializer->callback = callback; serializer->callback_data = callback_data; serialize_func (serializer); } /** * gdk_content_serializer_get_mime_type: * @serializer: a `GdkContentSerializer` * * Gets the mime type to serialize to. * * Returns: (transfer none): the mime type for the current operation */ const char * gdk_content_serializer_get_mime_type (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return serializer->mime_type; } /** * gdk_content_serializer_get_gtype: * @serializer: a `GdkContentSerializer` * * Gets the `GType` to of the object to serialize. * * Returns: the `GType` for the current operation */ GType gdk_content_serializer_get_gtype (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), G_TYPE_INVALID); return G_VALUE_TYPE (&serializer->value); } /** * gdk_content_serializer_get_value: * @serializer: a `GdkContentSerializer` * * Gets the `GValue` to read the object to serialize from. * * Returns: (transfer none): the `GValue` for the current operation */ const GValue * gdk_content_serializer_get_value (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return &serializer->value; } /** * gdk_content_serializer_get_output_stream: * @serializer: a `GdkContentSerializer` * * Gets the output stream for the current operation. * * This is the stream that was passed to [func@content_serialize_async]. * * Returns: (transfer none): the output stream for the current operation */ GOutputStream * gdk_content_serializer_get_output_stream (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return serializer->stream; } /** * gdk_content_serializer_get_priority: * @serializer: a `GdkContentSerializer` * * Gets the I/O priority for the current operation. * * This is the priority that was passed to [func@content_serialize_async]. * * Returns: the I/O priority for the current operation */ int gdk_content_serializer_get_priority (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), G_PRIORITY_DEFAULT); return serializer->priority; } /** * gdk_content_serializer_get_cancellable: * @serializer: a `GdkContentSerializer` * * Gets the cancellable for the current operation. * * This is the `GCancellable` that was passed to [func@content_serialize_async]. * * Returns: (transfer none): the cancellable for the current operation */ GCancellable * gdk_content_serializer_get_cancellable (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return serializer->cancellable; } /** * gdk_content_serializer_get_user_data: * @serializer: a `GdkContentSerializer` * * Gets the user data that was passed when the serializer was registered. * * Returns: (transfer none): the user data for this serializer */ gpointer gdk_content_serializer_get_user_data (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return serializer->user_data; } /** * gdk_content_serializer_set_task_data: * @serializer: a `GdkContentSerializer` * @data: data to associate with this operation * @notify: destroy notify for @data * * Associate data with the current serialization operation. */ void gdk_content_serializer_set_task_data (GdkContentSerializer *serializer, gpointer data, GDestroyNotify notify) { g_return_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer)); if (serializer->task_notify) serializer->task_notify (serializer->task_data); serializer->task_data = data; serializer->task_notify = notify; } /** * gdk_content_serializer_get_task_data: * @serializer: a `GdkContentSerializer` * * Gets the data that was associated with the current operation. * * See [method@Gdk.ContentSerializer.set_task_data]. * * Returns: (transfer none): the task data for @serializer */ gpointer gdk_content_serializer_get_task_data (GdkContentSerializer *serializer) { g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer), NULL); return serializer->task_data; } static gboolean gdk_content_serializer_emit_callback (gpointer data) { GdkContentSerializer *serializer = data; if (serializer->callback) { serializer->callback (NULL, G_ASYNC_RESULT (serializer), serializer->callback_data); } return G_SOURCE_REMOVE; } /** * gdk_content_serializer_return_success: * @serializer: a `GdkContentSerializer` * * Indicate that the serialization has been successfully completed. */ void gdk_content_serializer_return_success (GdkContentSerializer *serializer) { g_return_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer)); g_return_if_fail (!serializer->returned); serializer->returned = TRUE; g_idle_add_full (serializer->priority, gdk_content_serializer_emit_callback, serializer, g_object_unref); /* NB: the idle will destroy our reference */ } /** * gdk_content_serializer_return_error: * @serializer: a `GdkContentSerializer` * @error: (transfer full): a `GError` * * Indicate that the serialization has ended with an error. * * This function consumes @error. */ void gdk_content_serializer_return_error (GdkContentSerializer *serializer, GError *error) { g_return_if_fail (GDK_IS_CONTENT_SERIALIZER (serializer)); g_return_if_fail (!serializer->returned); g_return_if_fail (error != NULL); serializer->error = error; /* FIXME: naming */ gdk_content_serializer_return_success (serializer); } /** * gdk_content_register_serializer: * @type: the type of objects that the function can serialize * @mime_type: the mime type to serialize to * @serialize: the callback * @data: data that @serialize can access * @notify: destroy notify for @data * * Registers a function to serialize objects of a given type. */ void gdk_content_register_serializer (GType type, const char *mime_type, GdkContentSerializeFunc serialize, gpointer data, GDestroyNotify notify) { Serializer *serializer; g_return_if_fail (mime_type != NULL); g_return_if_fail (serialize != NULL); init (); serializer = g_slice_new0 (Serializer); serializer->mime_type = g_intern_string (mime_type); serializer->type = type; serializer->serialize = serialize; serializer->data = data; serializer->notify = notify; g_queue_push_tail (&serializers, serializer); } static Serializer * lookup_serializer (const char *mime_type, GType type) { GList *l; g_return_val_if_fail (mime_type != NULL, NULL); init (); mime_type = g_intern_string (mime_type); for (l = g_queue_peek_head_link (&serializers); l; l = l->next) { Serializer *serializer = l->data; if (serializer->mime_type == mime_type && serializer->type == type) return serializer; } return NULL; } /** * gdk_content_formats_union_serialize_gtypes: * @formats: (transfer full): a `GdkContentFormats` * * Add GTypes for the mime types in @formats for which serializers are * registered. * * Return: a new `GdkContentFormats` */ GdkContentFormats * gdk_content_formats_union_serialize_gtypes (GdkContentFormats *formats) { GdkContentFormatsBuilder *builder; GList *l; g_return_val_if_fail (formats != NULL, NULL); init (); builder = gdk_content_formats_builder_new (); gdk_content_formats_builder_add_formats (builder, formats); for (l = g_queue_peek_head_link (&serializers); l; l = l->next) { Serializer *serializer = l->data; if (gdk_content_formats_contain_mime_type (formats, serializer->mime_type)) gdk_content_formats_builder_add_gtype (builder, serializer->type); } gdk_content_formats_unref (formats); return gdk_content_formats_builder_free_to_formats (builder); } /** * gdk_content_formats_union_serialize_mime_types: * @formats: (transfer full): a `GdkContentFormats` * * Add mime types for GTypes in @formats for which serializers are * registered. * * Return: a new `GdkContentFormats` */ GdkContentFormats * gdk_content_formats_union_serialize_mime_types (GdkContentFormats *formats) { GdkContentFormatsBuilder *builder; GList *l; g_return_val_if_fail (formats != NULL, NULL); init (); builder = gdk_content_formats_builder_new (); gdk_content_formats_builder_add_formats (builder, formats); for (l = g_queue_peek_head_link (&serializers); l; l = l->next) { Serializer *serializer = l->data; if (gdk_content_formats_contain_gtype (formats, serializer->type)) gdk_content_formats_builder_add_mime_type (builder, serializer->mime_type); } gdk_content_formats_unref (formats); return gdk_content_formats_builder_free_to_formats (builder); } static void serialize_not_found (GdkContentSerializer *serializer) { GError *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Could not convert data from %s to %s", g_type_name (gdk_content_serializer_get_gtype (serializer)), gdk_content_serializer_get_mime_type (serializer)); gdk_content_serializer_return_error (serializer, error); } /** * gdk_content_serialize_async: * @stream: a `GOutputStream` to write the serialized content to * @mime_type: the mime type to serialize to * @value: the content to serialize * @io_priority: the I/O priority of the operation * @cancellable: (nullable): optional `GCancellable` object * @callback: (scope async): callback to call when the operation is done * @user_data: (closure): data to pass to the callback function * * Serialize content and write it to the given output stream, asynchronously. * * The default I/O priority is %G_PRIORITY_DEFAULT (i.e. 0), and lower numbers * indicate a higher priority. * * When the operation is finished, @callback will be called. You must then * call [func@Gdk.content_serialize_finish] to get the result of the operation. */ void gdk_content_serialize_async (GOutputStream *stream, const char *mime_type, const GValue *value, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { Serializer *serializer; g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); serializer = lookup_serializer (mime_type, G_VALUE_TYPE (value)); gdk_content_serializer_run (mime_type, value, stream, io_priority, cancellable, serializer ? serializer->serialize : serialize_not_found, serializer ? serializer->data : NULL, callback, user_data); } /** * gdk_content_serialize_finish: * @result: the `GAsyncResult` * @error: return location for an error * * Finishes a content serialization operation. * * Returns: %TRUE if the operation was successful, %FALSE if an * error occurred. In this case, @error is set */ gboolean gdk_content_serialize_finish (GAsyncResult *result, GError **error) { GdkContentSerializer *serializer; g_return_val_if_fail (GDK_IS_CONTENT_SERIALIZER (result), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); serializer = GDK_CONTENT_SERIALIZER (result); if (serializer->error) { if (error) *error = g_error_copy (serializer->error); return FALSE; } return TRUE; } /*** SERIALIZERS ***/ static void pixbuf_serializer_finish (GObject *source, GAsyncResult *res, gpointer serializer) { GError *error = NULL; if (!gdk_pixbuf_save_to_stream_finish (res, &error)) gdk_content_serializer_return_error (serializer, error); else gdk_content_serializer_return_success (serializer); } static void pixbuf_serializer (GdkContentSerializer *serializer) { const GValue *value; GdkPixbuf *pixbuf; const char *name; name = gdk_content_serializer_get_user_data (serializer); value = gdk_content_serializer_get_value (serializer); if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF)) { pixbuf = g_value_dup_object (value); } else if (G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE)) { GdkTexture *texture = g_value_get_object (value); cairo_surface_t *surface = gdk_texture_download_surface (texture); pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, gdk_texture_get_width (texture), gdk_texture_get_height (texture)); cairo_surface_destroy (surface); } else { g_assert_not_reached (); } gdk_pixbuf_save_to_stream_async (pixbuf, gdk_content_serializer_get_output_stream (serializer), name, gdk_content_serializer_get_cancellable (serializer), pixbuf_serializer_finish, serializer, g_str_equal (name, "png") ? "compression" : NULL, "2", NULL); g_object_unref (pixbuf); } static void string_serializer_finish (GObject *source, GAsyncResult *result, gpointer serializer) { GOutputStream *stream = G_OUTPUT_STREAM (source); GError *error = NULL; if (!g_output_stream_write_all_finish (stream, result, NULL, &error)) gdk_content_serializer_return_error (serializer, error); else gdk_content_serializer_return_success (serializer); } static void string_serializer (GdkContentSerializer *serializer) { GOutputStream *filter; GCharsetConverter *converter; GError *error = NULL; const char *text; converter = g_charset_converter_new (gdk_content_serializer_get_user_data (serializer), "utf-8", &error); if (converter == NULL) { gdk_content_serializer_return_error (serializer, error); return; } g_charset_converter_set_use_fallback (converter, TRUE); filter = g_converter_output_stream_new (gdk_content_serializer_get_output_stream (serializer), G_CONVERTER (converter)); g_object_unref (converter); text = g_value_get_string (gdk_content_serializer_get_value (serializer)); if (text == NULL) text = ""; g_output_stream_write_all_async (filter, text, strlen (text), gdk_content_serializer_get_priority (serializer), gdk_content_serializer_get_cancellable (serializer), string_serializer_finish, serializer); g_object_unref (filter); } static void file_serializer_finish (GObject *source, GAsyncResult *result, gpointer serializer) { GOutputStream *stream = G_OUTPUT_STREAM (source); GError *error = NULL; if (!g_output_stream_write_all_finish (stream, result, NULL, &error)) gdk_content_serializer_return_error (serializer, error); else gdk_content_serializer_return_success (serializer); } static void file_uri_serializer (GdkContentSerializer *serializer) { GFile *file; GString *str; const GValue *value; char *uri; str = g_string_new (NULL); value = gdk_content_serializer_get_value (serializer); if (G_VALUE_HOLDS (value, G_TYPE_FILE)) { file = g_value_get_object (gdk_content_serializer_get_value (serializer)); if (file) { uri = g_file_get_uri (file); g_string_append (str, uri); g_free (uri); } else { g_string_append (str, "# GTK does not crash when copying a NULL GFile!"); } g_string_append (str, "\r\n"); } else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) { GSList *l; for (l = g_value_get_boxed (value); l; l = l->next) { uri = g_file_get_uri (l->data); g_string_append (str, uri); g_free (uri); g_string_append (str, "\r\n"); } } g_output_stream_write_all_async (gdk_content_serializer_get_output_stream (serializer), str->str, str->len, gdk_content_serializer_get_priority (serializer), gdk_content_serializer_get_cancellable (serializer), file_serializer_finish, serializer); gdk_content_serializer_set_task_data (serializer, g_string_free (str, FALSE), g_free); } static void file_text_serializer (GdkContentSerializer *serializer) { const GValue *value; char *path = NULL; value = gdk_content_serializer_get_value (serializer); if (G_VALUE_HOLDS (value, G_TYPE_FILE)) { GFile *file; file = g_value_get_object (value); if (file) { path = g_file_get_path (file); if (path == NULL) path = g_file_get_uri (file); } } else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) { GString *str; GSList *l; str = g_string_new (NULL); for (l = g_value_get_boxed (value); l; l = l->next) { path = g_file_get_path (l->data); if (path == NULL) path = g_file_get_uri (l->data); g_string_append (str, path); g_free (path); if (l->next) g_string_append (str, " "); } path = g_string_free (str, FALSE); } g_assert (path != NULL); g_output_stream_write_all_async (gdk_content_serializer_get_output_stream (serializer), path, strlen (path), gdk_content_serializer_get_priority (serializer), gdk_content_serializer_get_cancellable (serializer), file_serializer_finish, serializer); gdk_content_serializer_set_task_data (serializer, path, g_free); } static void color_serializer_finish (GObject *source, GAsyncResult *result, gpointer serializer) { GOutputStream *stream = G_OUTPUT_STREAM (source); GError *error = NULL; if (!g_output_stream_write_all_finish (stream, result, NULL, &error)) gdk_content_serializer_return_error (serializer, error); else gdk_content_serializer_return_success (serializer); } static void color_serializer (GdkContentSerializer *serializer) { const GValue *value; GdkRGBA *rgba; guint16 *data; value = gdk_content_serializer_get_value (serializer); rgba = g_value_get_boxed (value); data = g_new0 (guint16, 4); if (rgba) { data[0] = (guint16) (rgba->red * 65535); data[1] = (guint16) (rgba->green * 65535); data[2] = (guint16) (rgba->blue * 65535); data[3] = (guint16) (rgba->alpha * 65535); } g_output_stream_write_all_async (gdk_content_serializer_get_output_stream (serializer), data, 4 * sizeof (guint16), gdk_content_serializer_get_priority (serializer), gdk_content_serializer_get_cancellable (serializer), color_serializer_finish, serializer); gdk_content_serializer_set_task_data (serializer, data, g_free); } static void init (void) { static gboolean initialized = FALSE; GSList *formats, *f; const char *charset; if (initialized) return; initialized = TRUE; formats = gdk_pixbuf_get_formats (); /* Make sure png comes first */ for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; char *name; name = gdk_pixbuf_format_get_name (fmt); if (g_str_equal (name, "png")) { formats = g_slist_delete_link (formats, f); formats = g_slist_prepend (formats, fmt); g_free (name); break; } g_free (name); } for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; char **mimes, **m; if (!gdk_pixbuf_format_is_writable (fmt)) continue; mimes = gdk_pixbuf_format_get_mime_types (fmt); for (m = mimes; *m; m++) { gdk_content_register_serializer (GDK_TYPE_TEXTURE, *m, pixbuf_serializer, gdk_pixbuf_format_get_name (fmt), g_free); gdk_content_register_serializer (GDK_TYPE_PIXBUF, *m, pixbuf_serializer, gdk_pixbuf_format_get_name (fmt), g_free); } g_strfreev (mimes); } g_slist_free (formats); #if defined(G_OS_UNIX) && !defined(__APPLE__) file_transfer_portal_register (); #endif gdk_content_register_serializer (G_TYPE_FILE, "text/uri-list", file_uri_serializer, NULL, NULL); gdk_content_register_serializer (G_TYPE_FILE, "text/plain;charset=utf-8", file_text_serializer, NULL, NULL); gdk_content_register_serializer (GDK_TYPE_FILE_LIST, "text/uri-list", file_uri_serializer, NULL, NULL); gdk_content_register_serializer (GDK_TYPE_FILE_LIST, "text/plain;charset=utf-8", file_text_serializer, NULL, NULL); gdk_content_register_serializer (G_TYPE_STRING, "text/plain;charset=utf-8", string_serializer, (gpointer) "utf-8", NULL); if (!g_get_charset (&charset)) { char *mime = g_strdup_printf ("text/plain;charset=%s", charset); gdk_content_register_serializer (G_TYPE_STRING, mime, string_serializer, (gpointer) charset, NULL); g_free (mime); } gdk_content_register_serializer (G_TYPE_STRING, "text/plain", string_serializer, (gpointer) "ASCII", NULL); gdk_content_register_serializer (GDK_TYPE_RGBA, "application/x-color", color_serializer, NULL, NULL); }