Files
grd/grd-clipboard.c
2026-02-13 13:06:50 +09:00

498 lines
15 KiB
C

/*
* Copyright (C) 2020-2021 Pascal Nowack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-clipboard.h"
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include "grd-session.h"
#define MAX_READ_TIME_MS 4000
typedef struct _ReadMimeTypeContentContext
{
GrdClipboard *clipboard;
int fd;
GCancellable *cancellable;
} ReadMimeTypeContentContext;
typedef struct _ReadMimeTypeContentResult
{
uint8_t *data;
uint32_t size;
} ReadMimeTypeContentResult;
typedef struct _GrdClipboardPrivate
{
GrdSession *session;
gboolean enabled;
GHashTable *client_mime_type_tables;
ReadMimeTypeContentResult *read_result;
GCancellable *read_cancellable;
unsigned int abort_read_source_id;
gboolean has_pending_read_operation;
GCond pending_read_cond;
GMutex pending_read_mutex;
} GrdClipboardPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GrdClipboard, grd_clipboard, G_TYPE_OBJECT)
static void
handle_read_result (GrdClipboard *clipboard,
ReadMimeTypeContentResult *read_result)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->has_pending_read_operation = FALSE;
/* Discard the read_result, if the clipboard is already disabled. */
if (!priv->enabled)
return;
if (read_result->data)
g_debug ("Clipboard[SelectionRead]: Request successful");
else
g_debug ("Clipboard[SelectionRead]: Request failed");
klass->submit_requested_server_content (clipboard, read_result->data,
read_result->size);
}
static void
flush_pending_read_result (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentResult *read_result;
g_mutex_lock (&priv->pending_read_mutex);
while (!priv->read_result)
g_cond_wait (&priv->pending_read_cond, &priv->pending_read_mutex);
g_mutex_unlock (&priv->pending_read_mutex);
read_result = g_steal_pointer (&priv->read_result);
handle_read_result (clipboard, read_result);
g_free (read_result);
}
static void
abort_current_read_operation (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
if (!priv->has_pending_read_operation)
return;
g_debug ("Clipboard[SelectionRead]: Aborting current read operation");
g_cancellable_cancel (priv->read_cancellable);
g_clear_object (&priv->read_cancellable);
g_clear_handle_id (&priv->abort_read_source_id, g_source_remove);
flush_pending_read_result (clipboard);
}
void
grd_clipboard_update_server_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_tables)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
GList *l;
g_debug ("Clipboard[SetSelection]: Updating servers clipboard");
for (l = mime_type_tables; l; l = l->next)
{
GrdMimeTypeTable *mime_type_table = l->data;
GrdMimeType mime_type;
mime_type = mime_type_table->mime_type;
g_debug ("Clipboard[SetSelection]: Update contains mime type %s",
grd_mime_type_to_string (mime_type));
g_hash_table_insert (priv->client_mime_type_tables,
GUINT_TO_POINTER (mime_type), mime_type_table);
}
if (!priv->enabled)
{
g_debug ("Clipboard[EnableClipboard]: Enabling clipboard");
priv->enabled = grd_session_enable_clipboard (priv->session,
clipboard, mime_type_tables);
if (priv->enabled)
g_debug ("Clipboard[EnableClipboard]: Clipboard enabled");
else
g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled");
}
else
{
abort_current_read_operation (clipboard);
if (mime_type_tables)
grd_session_set_selection (priv->session, mime_type_tables);
}
g_debug ("Clipboard[SetSelection]: Update complete");
g_list_free (mime_type_tables);
}
static void
async_read_operation_complete (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GrdClipboard *clipboard = user_data;
GrdClipboardPrivate *priv;
ReadMimeTypeContentContext *read_context =
g_task_get_task_data (G_TASK (result));
ReadMimeTypeContentResult *read_result;
if (g_cancellable_is_cancelled (read_context->cancellable))
return;
priv = grd_clipboard_get_instance_private (clipboard);
g_assert (priv->has_pending_read_operation);
g_clear_object (&priv->read_cancellable);
g_clear_handle_id (&priv->abort_read_source_id, g_source_remove);
read_result = g_steal_pointer (&priv->read_result);
handle_read_result (clipboard, read_result);
g_free (read_result);
}
static void
clear_read_context (gpointer data)
{
ReadMimeTypeContentContext *read_context = data;
g_object_unref (read_context->cancellable);
g_free (data);
}
static void
read_mime_type_content_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
ReadMimeTypeContentContext *read_context = task_data;
GrdClipboard *clipboard = read_context->clipboard;
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentResult *read_result;
GInputStream *input_stream;
GArray *data;
gboolean success = FALSE;
g_autoptr (GError) error = NULL;
input_stream = g_unix_input_stream_new (read_context->fd, TRUE);
data = g_array_new (FALSE, TRUE, sizeof (uint8_t));
while (TRUE)
{
int len;
uint8_t buffer[1024];
len = g_input_stream_read (input_stream, buffer, G_N_ELEMENTS (buffer),
read_context->cancellable, &error);
if (len < 0)
{
g_warning ("Clipboard[SelectionRead]: Failed to read mime type "
"content: %s", error->message);
break;
}
else if (len == 0)
{
success = TRUE;
break;
}
else
{
g_array_append_vals (data, buffer, len);
}
}
read_result = g_malloc0 (sizeof (ReadMimeTypeContentResult));
if (success && data->len > 0)
{
read_result->size = data->len;
read_result->data = (uint8_t *) g_array_free (data, FALSE);
}
else
{
g_array_free (data, TRUE);
}
g_object_unref (input_stream);
g_mutex_lock (&priv->pending_read_mutex);
priv->read_result = read_result;
g_cond_signal (&priv->pending_read_cond);
g_mutex_unlock (&priv->pending_read_mutex);
g_task_return_pointer (task, NULL, NULL);
}
static gboolean
abort_mime_type_content_read (gpointer user_data)
{
GrdClipboard *clipboard = user_data;
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_debug ("Clipboard[SelectionRead]: Aborting current read operation "
"(Timeout reached)");
g_assert (priv->has_pending_read_operation);
g_assert (priv->abort_read_source_id);
priv->abort_read_source_id = 0;
abort_current_read_operation (clipboard);
return G_SOURCE_REMOVE;
}
static void
read_mime_type_content_async (GrdClipboard *clipboard,
int fd)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
ReadMimeTypeContentContext *read_context;
GTask *task;
abort_current_read_operation (clipboard);
priv->read_cancellable = g_cancellable_new ();
priv->has_pending_read_operation = TRUE;
g_assert (!priv->read_result);
read_context = g_malloc0 (sizeof (ReadMimeTypeContentContext));
read_context->clipboard = clipboard;
read_context->fd = fd;
read_context->cancellable = g_object_ref (priv->read_cancellable);
task = g_task_new (NULL, NULL, async_read_operation_complete, clipboard);
g_task_set_task_data (task, read_context, clear_read_context);
g_task_run_in_thread (task, read_mime_type_content_in_thread);
g_object_unref (task);
priv->abort_read_source_id =
g_timeout_add (MAX_READ_TIME_MS, abort_mime_type_content_read, clipboard);
}
void
grd_clipboard_request_server_content_for_mime_type_async (GrdClipboard *clipboard,
GrdMimeType mime_type)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
int fd;
g_return_if_fail (klass->submit_requested_server_content);
g_assert (priv->enabled);
g_debug ("Clipboard[SelectionRead]: Requesting data from servers clipboard"
" (mime type: %s)", grd_mime_type_to_string (mime_type));
fd = grd_session_selection_read (priv->session, mime_type);
if (fd == -1)
{
g_debug ("Clipboard[SelectionRead]: Request failed");
klass->submit_requested_server_content (clipboard, NULL, 0);
return;
}
read_mime_type_content_async (clipboard, fd);
}
void
grd_clipboard_initialize (GrdClipboard *clipboard,
GrdSession *session)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->session = session;
}
void
grd_clipboard_maybe_enable_clipboard (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_debug ("Clipboard[EnableClipboard]: Enabling clipboard");
if (priv->enabled)
{
g_debug ("Clipboard[EnableClipboard]: Clipboard already enabled");
return;
}
priv->enabled = grd_session_enable_clipboard (priv->session, clipboard, NULL);
if (priv->enabled)
g_debug ("Clipboard[EnableClipboard]: Clipboard enabled");
else
g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled");
}
void
grd_clipboard_disable_clipboard (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
if (!priv->enabled)
return;
g_debug ("Clipboard[DisableClipboard]: Disabling clipboard");
grd_session_disable_clipboard (priv->session);
priv->enabled = FALSE;
}
void
grd_clipboard_update_client_mime_type_list (GrdClipboard *clipboard,
GList *mime_type_list)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
/**
* Ensure that the response with the read mime type content is sent to the
* client first, before sending the new mime type list
*/
abort_current_read_operation (clipboard);
g_assert (priv->enabled);
if (!klass->update_client_mime_type_list)
return;
g_hash_table_remove_all (priv->client_mime_type_tables);
g_debug ("Clipboard[SelectionOwnerChanged]: Updating clients clipboard");
klass->update_client_mime_type_list (clipboard, mime_type_list);
g_debug ("Clipboard[SelectionOwnerChanged]: Update complete");
}
void
grd_clipboard_submit_client_content_for_mime_type (GrdClipboard *clipboard,
unsigned int serial,
const uint8_t *data,
uint32_t size)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_assert (priv->enabled);
if (data && size)
g_debug ("Clipboard[SelectionTransfer]: Request for serial %u was successful", serial);
else
g_debug ("Clipboard[SelectionTransfer]: Request for serial %u failed", serial);
grd_session_selection_write (priv->session, serial, data, size);
}
void
grd_clipboard_request_client_content_for_mime_type (GrdClipboard *clipboard,
GrdMimeType mime_type,
unsigned int serial)
{
GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
GrdMimeTypeTable *mime_type_table = NULL;
g_assert (priv->enabled);
g_return_if_fail (klass->request_client_content_for_mime_type);
g_debug ("Clipboard[SelectionTransfer]: Requesting data from clients clipboard"
" (mime type: %s, serial: %u)",
grd_mime_type_to_string (mime_type), serial);
mime_type_table = g_hash_table_lookup (priv->client_mime_type_tables,
GUINT_TO_POINTER (mime_type));
if (!mime_type_table)
{
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
NULL, 0);
return;
}
klass->request_client_content_for_mime_type (clipboard, mime_type_table,
serial);
}
static void
free_mime_type_table (gpointer data)
{
GrdMimeTypeTable *mime_type_table = data;
g_free (mime_type_table);
}
static void
grd_clipboard_dispose (GObject *object)
{
GrdClipboard *clipboard = GRD_CLIPBOARD (object);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
abort_current_read_operation (clipboard);
g_clear_pointer (&priv->client_mime_type_tables, g_hash_table_destroy);
G_OBJECT_CLASS (grd_clipboard_parent_class)->dispose (object);
}
static void
grd_clipboard_finalize (GObject *object)
{
GrdClipboard *clipboard = GRD_CLIPBOARD (object);
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
g_mutex_clear (&priv->pending_read_mutex);
g_cond_clear (&priv->pending_read_cond);
G_OBJECT_CLASS (grd_clipboard_parent_class)->finalize (object);
}
static void
grd_clipboard_init (GrdClipboard *clipboard)
{
GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard);
priv->client_mime_type_tables = g_hash_table_new_full (NULL, NULL, NULL,
free_mime_type_table);
g_cond_init (&priv->pending_read_cond);
g_mutex_init (&priv->pending_read_mutex);
}
static void
grd_clipboard_class_init (GrdClipboardClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_clipboard_dispose;
object_class->finalize = grd_clipboard_finalize;
}