/*
* Copyright (C) 2018 Red Hat
*
* 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, see .
*
* Author: Carlos Garnacho
*/
#include "config.h"
#include "core/meta-clipboard-manager.h"
#include "core/meta-selection-private.h"
#include "meta/meta-selection-source-memory.h"
#define MAX_TEXT_SIZE (4 * 1024 * 1024) /* 4MB */
#define MAX_IMAGE_SIZE (200 * 1024 * 1024) /* 200MB */
/* Supported mimetype globs, from least to most preferred */
static struct {
const char *mimetype_glob;
ssize_t max_transfer_size;
} supported_mimetypes[] = {
{ "image/tiff", MAX_IMAGE_SIZE },
{ "image/bmp", MAX_IMAGE_SIZE },
{ "image/gif", MAX_IMAGE_SIZE },
{ "image/jpeg", MAX_IMAGE_SIZE },
{ "image/webp", MAX_IMAGE_SIZE },
{ "image/png", MAX_IMAGE_SIZE },
{ "image/svg+xml", MAX_IMAGE_SIZE },
{ "text/plain", MAX_TEXT_SIZE },
{ "text/plain;charset=utf-8", MAX_TEXT_SIZE },
};
static gboolean
mimetype_match (const char *mimetype,
int *idx,
gssize *max_transfer_size)
{
int i;
for (i = 0; i < G_N_ELEMENTS (supported_mimetypes); i++)
{
if (g_pattern_match_simple (supported_mimetypes[i].mimetype_glob, mimetype))
{
*max_transfer_size = supported_mimetypes[i].max_transfer_size;
*idx = i;
return TRUE;
}
}
return FALSE;
}
static void
transfer_cb (MetaSelection *selection,
GAsyncResult *result,
GOutputStream *output)
{
MetaDisplay *display = meta_selection_get_display (selection);
GError *error = NULL;
if (!meta_selection_transfer_finish (selection, result, &error))
{
g_warning ("Failed to store clipboard: %s", error->message);
g_error_free (error);
g_object_unref (output);
return;
}
g_output_stream_close (output, NULL, NULL);
display->saved_clipboard =
g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output));
g_object_unref (output);
}
static void
owner_changed_cb (MetaSelection *selection,
MetaSelectionType selection_type,
MetaSelectionSource *new_owner,
MetaDisplay *display)
{
if (selection_type != META_SELECTION_CLIPBOARD)
return;
if (new_owner && new_owner != display->selection_source)
{
GOutputStream *output;
GList *mimetypes, *l;
int best_idx = -1;
const char *best = NULL;
ssize_t transfer_size = -1;
/* New selection source, find the best mimetype in order to
* keep a copy of it.
*/
g_clear_object (&display->selection_source);
g_clear_pointer (&display->saved_clipboard_mimetype, g_free);
g_clear_pointer (&display->saved_clipboard, g_bytes_unref);
mimetypes = meta_selection_get_mimetypes (selection, selection_type);
for (l = mimetypes; l; l = l->next)
{
gssize max_transfer_size;
int idx;
if (!mimetype_match (l->data, &idx, &max_transfer_size))
continue;
if (best_idx < idx)
{
best_idx = idx;
best = l->data;
transfer_size = max_transfer_size;
}
}
if (best_idx < 0)
{
g_list_free_full (mimetypes, g_free);
return;
}
display->saved_clipboard_mimetype = g_strdup (best);
g_list_free_full (mimetypes, g_free);
output = g_memory_output_stream_new_resizable ();
meta_selection_transfer_async (selection,
META_SELECTION_CLIPBOARD,
display->saved_clipboard_mimetype,
transfer_size,
output,
NULL,
(GAsyncReadyCallback) transfer_cb,
output);
}
else if (!new_owner && display->saved_clipboard)
{
/* Old owner is gone, time to take over */
new_owner = meta_selection_source_memory_new (display->saved_clipboard_mimetype,
display->saved_clipboard);
g_set_object (&display->selection_source, new_owner);
meta_selection_set_owner (selection, selection_type, new_owner);
g_object_unref (new_owner);
}
}
void
meta_clipboard_manager_init (MetaDisplay *display)
{
MetaSelection *selection;
selection = meta_display_get_selection (display);
g_signal_connect_after (selection, "owner-changed",
G_CALLBACK (owner_changed_cb), display);
}
void
meta_clipboard_manager_shutdown (MetaDisplay *display)
{
MetaSelection *selection;
g_clear_object (&display->selection_source);
g_clear_pointer (&display->saved_clipboard, g_bytes_unref);
g_clear_pointer (&display->saved_clipboard_mimetype, g_free);
selection = meta_display_get_selection (display);
g_signal_handlers_disconnect_by_func (selection, owner_changed_cb, display);
}