Compare commits

...

5 Commits

Author SHA1 Message Date
Benjamin Otte
cdf990617f testsuite: Add a basic test for remote clipboards
Unfortunately, Wayland doesn't allow access to the clipboard unless the
display has focus, so no chance of testing it there.

But it works elsewhere.
2020-09-18 06:10:03 +02:00
Benjamin Otte
57431a4afd contentformats: Guarantee that we return NULL for no elements
... and add an assertion that checks this does happen.
2020-09-18 06:10:03 +02:00
Benjamin Otte
93862561c9 contentserializer: Always close output stream
This makes the behavior consistent and ensures the data is flushed and
any potential errors are properly reported back.

Previously we would just unref() it, which would just lose all the
errors.
2020-09-18 06:10:03 +02:00
Benjamin Otte
5da179e4e4 x11: Don't consume too many references
close_async() would consume a reference via invoke_close(), so take a
reference before calling it.
2020-09-18 06:10:03 +02:00
Benjamin Otte
2489e0047a x11: Don't deadlock when flush()ing from the main loop
Instead, emit a warning that it's not supported and don't flush
anything.

Of course, this usually happens on close(), so we are now dropping the
data, which is still unfortunate, but a step up from deadlocking.
2020-09-18 06:10:02 +02:00
4 changed files with 199 additions and 28 deletions

View File

@@ -121,6 +121,9 @@ gdk_content_formats_new_take (GType * gtypes,
GdkContentFormats *result = g_slice_new0 (GdkContentFormats);
result->ref_count = 1;
g_assert (n_mime_types > 0 || mime_types == NULL);
g_assert (n_gtypes > 0 || gtypes == NULL);
result->gtypes = gtypes;
result->n_gtypes = n_gtypes;
result->mime_types = mime_types;
@@ -650,19 +653,33 @@ gdk_content_formats_builder_to_formats (GdkContentFormatsBuilder *builder)
g_return_val_if_fail (builder != NULL, NULL);
gtypes = g_new (GType, builder->n_gtypes + 1);
i = builder->n_gtypes;
gtypes[i--] = G_TYPE_INVALID;
/* add backwards because most important type is last in the list */
for (l = builder->gtypes; l; l = l->next)
gtypes[i--] = GPOINTER_TO_SIZE (l->data);
if (builder->n_gtypes == 0)
{
gtypes = NULL;
}
else
{
gtypes = g_new (GType, builder->n_gtypes + 1);
i = builder->n_gtypes;
gtypes[i--] = G_TYPE_INVALID;
/* add backwards because most important type is last in the list */
for (l = builder->gtypes; l; l = l->next)
gtypes[i--] = GPOINTER_TO_SIZE (l->data);
}
mime_types = g_new (const char *, builder->n_mime_types + 1);
i = builder->n_mime_types;
mime_types[i--] = NULL;
/* add backwards because most important type is last in the list */
for (l = builder->mime_types; l; l = l->next)
mime_types[i--] = l->data;
if (builder->n_mime_types == 0)
{
mime_types = NULL;
}
else
{
mime_types = g_new (const char *, builder->n_mime_types + 1);
i = builder->n_mime_types;
mime_types[i--] = NULL;
/* add backwards because most important type is last in the list */
for (l = builder->mime_types; l; l = l->next)
mime_types[i--] = l->data;
}
result = gdk_content_formats_new_take (gtypes, builder->n_gtypes,
mime_types, builder->n_mime_types);

View File

@@ -317,17 +317,23 @@ gdk_content_serializer_get_task_data (GdkContentSerializer *serializer)
return serializer->task_data;
}
static gboolean
gdk_content_serializer_emit_callback (gpointer data)
static void
gdk_content_serializer_close_finish (GObject *stream,
GAsyncResult *res,
gpointer data)
{
GdkContentSerializer *serializer = data;
g_output_stream_close_finish (G_OUTPUT_STREAM (stream),
res,
serializer->error ? NULL : &serializer->error);
if (serializer->callback)
{
serializer->callback (NULL, G_ASYNC_RESULT (serializer), serializer->callback_data);
}
return G_SOURCE_REMOVE;
g_object_unref (serializer);
}
/**
@@ -335,6 +341,8 @@ gdk_content_serializer_emit_callback (gpointer data)
* @serializer: a #GdkContentSerializer
*
* Indicate that the serialization has been successfully completed.
*
* The serializer will close the output stream and report success to the callback.
*/
void
gdk_content_serializer_return_success (GdkContentSerializer *serializer)
@@ -343,11 +351,11 @@ gdk_content_serializer_return_success (GdkContentSerializer *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 */
g_output_stream_close_async (serializer->stream,
serializer->priority,
serializer->cancellable,
gdk_content_serializer_close_finish,
serializer);
}
/**
@@ -523,8 +531,9 @@ serialize_not_found (GdkContentSerializer *serializer)
* @user_data: (closure): data to pass to the callback function
*
* Serialize content and write it to the given output stream, asynchronously.
* When the operation is finished, @callback will be called. You can then
* call gdk_content_serialize_finish() to get the result of the operation.
* When the operation is finished, the @stream will be closed and @callback
* will be called. You can then call gdk_content_serialize_finish() to get
* the result of the operation.
*/
void
gdk_content_serialize_async (GOutputStream *stream,

View File

@@ -439,8 +439,33 @@ gdk_x11_selection_output_stream_flush (GOutputStream *output_stream,
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);
while (gdk_x11_selection_output_stream_needs_flush_unlocked (stream))
{
/* We cannot do sync flushes as we need to run the main loop to do the flushing. */
if (g_main_context_is_owner (NULL))
{
#define ERROR_MESSAGE \
"Synchronous flushing of X11 data streams from the main loop is not possible.\n" \
"Use g_output_stream_flush_async() or g_output_stream_close_async() to avoid data loss."
g_mutex_unlock (&priv->mutex);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ERROR_MESSAGE);
/* We also warn here instead of just erroring out because this happens when people forget
* to explicitly close a data stream and just unref() it as dispose() runs a sync close()
* which does a sync flush().
*/
g_warning (ERROR_MESSAGE);
return FALSE;
#undef ERROR_MESSAGE
}
else
{
g_cond_wait (&priv->cond, &priv->mutex);
}
}
g_mutex_unlock (&priv->mutex);
return TRUE;
@@ -531,7 +556,7 @@ gdk_x11_selection_output_stream_close_async (GOutputStream *stream,
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);
gdk_x11_selection_output_stream_invoke_close (g_object_ref (stream));
g_task_return_boolean (task, TRUE);
g_object_unref (task);

View File

@@ -2,10 +2,46 @@
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#endif
static void
text_received (GObject *source,
value_received (GObject *source,
GAsyncResult *res,
gpointer data)
{
GdkClipboard *clipboard = GDK_CLIPBOARD (source);
const GValue *value;
GValue *expected = data;
GError *error = NULL;
value = gdk_clipboard_read_value_finish (clipboard, res, &error);
g_assert_nonnull (value);
g_assert_no_error (error);
g_assert_cmpuint (G_VALUE_TYPE (value), ==, G_VALUE_TYPE (expected));
if (G_VALUE_TYPE (value) == G_TYPE_STRING)
{
g_assert_cmpstr (g_value_get_string (value), ==, g_value_get_string (expected));
}
else
{
/* Add a comparison for the missing type here */
g_assert_cmpuint (G_VALUE_TYPE (value), ==, G_TYPE_INVALID);
}
g_value_unset (expected);
g_main_context_wakeup (NULL);
}
static void
text_received (GObject *source,
GAsyncResult *res,
gpointer data)
gpointer data)
{
GdkClipboard *clipboard = GDK_CLIPBOARD (source);
gboolean *done = data;
@@ -70,14 +106,98 @@ test_clipboard_basic (void)
g_value_unset (&value);
}
static void
remote_clipboard_sync_changed_cb (GdkClipboard *clipboard,
gboolean *done)
{
GdkContentFormats *formats = gdk_clipboard_get_formats (clipboard);
/* X11 needs to query formats first and reports empty until then */
if (gdk_content_formats_get_gtypes (formats, NULL) == NULL &&
gdk_content_formats_get_mime_types (formats, NULL) == NULL)
return;
*done = TRUE;
}
static void
remote_clipboard_sync (GdkClipboard *clipboard)
{
guint signal;
gboolean done;
done = FALSE;
signal = g_signal_connect (clipboard, "changed", G_CALLBACK (remote_clipboard_sync_changed_cb), &done);
while (!done)
g_main_context_iteration (NULL, TRUE);
g_signal_handler_disconnect (clipboard, signal);
}
static void
test_remote_basic (gconstpointer data)
{
GdkDisplay *display, *remote_display;
GdkClipboard *clipboard, *remote_clipboard;
GdkContentFormats *formats;
GValue value = G_VALUE_INIT;
display = gdk_display_get_default ();
clipboard = gdk_display_get_clipboard (display);
remote_display = GDK_DISPLAY (data);
remote_clipboard = gdk_display_get_clipboard (remote_display);
g_assert_true (gdk_clipboard_get_display (clipboard) == display);
g_assert_true (gdk_clipboard_get_display (remote_clipboard) == remote_display);
gdk_clipboard_set_text (clipboard, "testing, 1, 2");
remote_clipboard_sync (remote_clipboard);
g_assert_true (gdk_clipboard_is_local (clipboard));
g_assert_false (gdk_clipboard_is_local (remote_clipboard));
g_assert_null (gdk_clipboard_get_content (remote_clipboard));
formats = gdk_clipboard_get_formats (remote_clipboard);
g_assert_true (gdk_content_formats_contain_gtype (formats, G_TYPE_STRING));
g_assert_true (gdk_content_formats_contain_mime_type (formats, "text/plain"));
g_value_init (&value, G_TYPE_STRING);
g_value_set_string (&value, "testing, 1, 2");
gdk_clipboard_read_value_async (remote_clipboard, G_TYPE_STRING, G_PRIORITY_DEFAULT, NULL, value_received, &value);
while (G_IS_VALUE (&value))
g_main_context_iteration (NULL, TRUE);
g_assert_null (gdk_clipboard_get_content (remote_clipboard));
}
int
main (int argc, char *argv[])
{
GdkDisplay *remote_display;
int result;
g_test_init (&argc, &argv, NULL);
gtk_init ();
g_test_add_func ("/clipboard/basic", test_clipboard_basic);
return g_test_run ();
remote_display = gdk_display_open (NULL);
#ifdef GDK_WINDOWING_WAYLAND
if (!GDK_IS_WAYLAND_DISPLAY (remote_display))
#else
if (true)
#endif
{
g_test_add_data_func ("/clipboard/remote/basic", remote_display, test_remote_basic);
}
result = g_test_run ();
g_object_unref (remote_display);
return result;
}