mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
498 lines
15 KiB
C
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;
|
|
}
|