thunar-usagebars/thunar/thunar-clipboard-manager.c
4aiman df58603323 thunar 4.20.7 upstream
Tree of tag thunar-4.20.7 (commit df0e054) from https://gitlab.xfce.org/xfce/thunar.
Upstream history is not included here to keep the repository small.
2026-11-04 01:40:30 +03:00

750 lines
24 KiB
C

/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
* Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
*
* 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include "thunar/thunar-application.h"
#include "thunar/thunar-clipboard-manager.h"
#include "thunar/thunar-dialogs.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-private.h"
#include <libxfce4util/libxfce4util.h>
enum
{
PROP_0,
PROP_CAN_PASTE,
};
enum
{
CHANGED,
LAST_SIGNAL,
};
enum
{
TARGET_TEXT_URI_LIST,
TARGET_GNOME_COPIED_FILES,
TARGET_UTF8_STRING,
};
static void
thunar_clipboard_manager_finalize (GObject *object);
static void
thunar_clipboard_manager_dispose (GObject *object);
static void
thunar_clipboard_manager_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void
thunar_clipboard_manager_file_destroyed (ThunarFile *file,
ThunarClipboardManager *manager);
static void
thunar_clipboard_manager_owner_changed (GtkClipboard *clipboard,
GdkEventOwnerChange *event,
ThunarClipboardManager *manager);
static void
thunar_clipboard_manager_contents_received (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer user_data);
static void
thunar_clipboard_manager_targets_received (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer user_data);
static void
thunar_clipboard_manager_get_callback (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint info,
gpointer user_data);
static void
thunar_clipboard_manager_clear_callback (GtkClipboard *clipboard,
gpointer user_data);
static void
thunar_clipboard_manager_transfer_files (ThunarClipboardManager *manager,
gboolean copy,
GList *files);
struct _ThunarClipboardManagerClass
{
GObjectClass __parent__;
void (*changed) (ThunarClipboardManager *manager);
};
struct _ThunarClipboardManager
{
GObject __parent__;
GtkClipboard *clipboard;
gboolean can_paste;
GdkAtom x_special_gnome_copied_files;
gboolean files_cutted;
GList *files;
};
typedef struct
{
ThunarClipboardManager *manager;
GFile *target_file;
GtkWidget *widget;
GClosure *new_files_closure;
} ThunarClipboardPasteRequest;
static const GtkTargetEntry clipboard_targets[] = {
{ "text/uri-list", 0, TARGET_TEXT_URI_LIST },
{ "x-special/gnome-copied-files", 0, TARGET_GNOME_COPIED_FILES },
{ "UTF8_STRING", 0, TARGET_UTF8_STRING }
};
static GQuark thunar_clipboard_manager_quark = 0;
static guint manager_signals[LAST_SIGNAL];
G_DEFINE_TYPE (ThunarClipboardManager, thunar_clipboard_manager, G_TYPE_OBJECT)
static void
thunar_clipboard_manager_class_init (ThunarClipboardManagerClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = thunar_clipboard_manager_dispose;
gobject_class->finalize = thunar_clipboard_manager_finalize;
gobject_class->get_property = thunar_clipboard_manager_get_property;
/**
* ThunarClipboardManager:can-paste:
*
* This property tells whether the current clipboard content of
* this #ThunarClipboardManager can be pasted into a folder
* displayed by a #ThunarView.
**/
g_object_class_install_property (gobject_class,
PROP_CAN_PASTE,
g_param_spec_boolean ("can-paste", "can-paste", "can-paste",
FALSE,
EXO_PARAM_READABLE));
/**
* ThunarClipboardManager::changed:
* @manager : a #ThunarClipboardManager.
*
* This signal is emitted whenever the contents of the
* clipboard associated with @manager changes.
**/
manager_signals[CHANGED] =
g_signal_new (I_ ("changed"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (ThunarClipboardManagerClass, changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
thunar_clipboard_manager_init (ThunarClipboardManager *manager)
{
manager->x_special_gnome_copied_files = gdk_atom_intern_static_string ("x-special/gnome-copied-files");
}
static void
thunar_clipboard_manager_dispose (GObject *object)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (object);
/* store the clipboard if we still own it and a clipboard
* manager is running (gtk_clipboard_store checks this) */
if (gtk_clipboard_get_owner (manager->clipboard) == object
&& manager->files != NULL)
{
gtk_clipboard_set_can_store (manager->clipboard, clipboard_targets,
G_N_ELEMENTS (clipboard_targets));
gtk_clipboard_store (manager->clipboard);
}
(*G_OBJECT_CLASS (thunar_clipboard_manager_parent_class)->dispose) (object);
}
static void
thunar_clipboard_manager_finalize (GObject *object)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (object);
GList *lp;
/* release any pending files */
for (lp = manager->files; lp != NULL; lp = lp->next)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (lp->data), thunar_clipboard_manager_file_destroyed, manager);
g_object_unref (G_OBJECT (lp->data));
}
g_list_free (manager->files);
/* disconnect from the clipboard */
g_signal_handlers_disconnect_by_func (G_OBJECT (manager->clipboard), thunar_clipboard_manager_owner_changed, manager);
g_object_set_qdata (G_OBJECT (manager->clipboard), thunar_clipboard_manager_quark, NULL);
g_object_unref (G_OBJECT (manager->clipboard));
(*G_OBJECT_CLASS (thunar_clipboard_manager_parent_class)->finalize) (object);
}
static void
thunar_clipboard_manager_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (object);
switch (prop_id)
{
case PROP_CAN_PASTE:
g_value_set_boolean (value, thunar_clipboard_manager_get_can_paste (manager));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
thunar_clipboard_manager_file_destroyed (ThunarFile *file,
ThunarClipboardManager *manager)
{
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (g_list_find (manager->files, file) != NULL);
/* remove the file from our list */
manager->files = g_list_remove (manager->files, file);
/* disconnect from the file */
g_signal_handlers_disconnect_by_func (G_OBJECT (file), thunar_clipboard_manager_file_destroyed, manager);
g_object_unref (G_OBJECT (file));
}
static void
thunar_clipboard_manager_owner_changed (GtkClipboard *clipboard,
GdkEventOwnerChange *event,
ThunarClipboardManager *manager)
{
_thunar_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (manager->clipboard == clipboard);
/* need to take a reference on the manager, because the clipboards
* "targets received callback" mechanism is not cancellable.
*/
g_object_ref (G_OBJECT (manager));
/* request the list of supported targets from the new owner */
gtk_clipboard_request_contents (clipboard, gdk_atom_intern_static_string ("TARGETS"),
thunar_clipboard_manager_targets_received, manager);
}
static void
thunar_clipboard_manager_contents_received (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer user_data)
{
ThunarClipboardPasteRequest *request = user_data;
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (request->manager);
ThunarApplication *application;
gboolean path_copy = TRUE;
GList *file_list = NULL;
gchar *data;
/* check whether the retrieval worked */
if (G_LIKELY (gtk_selection_data_get_length (selection_data) > 0))
{
/* be sure the selection data is zero-terminated */
data = (gchar *) gtk_selection_data_get_data (selection_data);
data[gtk_selection_data_get_length (selection_data)] = '\0';
/* check whether to copy or move */
if (g_ascii_strncasecmp (data, "copy\n", 5) == 0)
{
path_copy = TRUE;
data += 5;
}
else if (g_ascii_strncasecmp (data, "cut\n", 4) == 0)
{
path_copy = FALSE;
data += 4;
}
/* determine the path list stored with the selection */
file_list = thunar_g_file_list_new_from_string (data);
}
/* perform the action if possible */
if (G_LIKELY (file_list != NULL))
{
application = thunar_application_get ();
if (G_LIKELY (path_copy))
thunar_application_copy_into (application, request->widget, file_list, request->target_file, THUNAR_OPERATION_LOG_OPERATIONS, request->new_files_closure);
else
thunar_application_move_into (application, request->widget, file_list, request->target_file, THUNAR_OPERATION_LOG_OPERATIONS, request->new_files_closure);
g_object_unref (G_OBJECT (application));
thunar_g_list_free_full (file_list);
/* clear the clipboard if it contained "cutted data"
* (gtk_clipboard_clear takes care of not clearing
* the selection if we don't own it)
*/
if (G_UNLIKELY (!path_copy))
gtk_clipboard_clear (manager->clipboard);
/* check the contents of the clipboard again if either the Xserver or
* our GTK+ version doesn't support the XFixes extension */
if (!gdk_display_supports_selection_notification (gtk_clipboard_get_display (manager->clipboard)))
{
thunar_clipboard_manager_owner_changed (manager->clipboard, NULL, manager);
}
}
else
{
/* tell the user that we cannot paste */
thunar_dialogs_show_error (request->widget, NULL, _("There is nothing on the clipboard to paste"));
}
/* free the request */
if (G_LIKELY (request->widget != NULL))
g_object_remove_weak_pointer (G_OBJECT (request->widget), (gpointer) &request->widget);
if (G_LIKELY (request->new_files_closure != NULL))
g_closure_unref (request->new_files_closure);
g_object_unref (G_OBJECT (request->manager));
g_object_unref (request->target_file);
g_slice_free (ThunarClipboardPasteRequest, request);
}
static void
thunar_clipboard_manager_targets_received (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer user_data)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (user_data);
GdkAtom *targets;
gint n_targets;
gint n;
_thunar_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (manager->clipboard == clipboard);
/* reset the "can-paste" state */
manager->can_paste = FALSE;
/* check the list of targets provided by the owner */
if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets))
{
for (n = 0; n < n_targets; ++n)
if (targets[n] == manager->x_special_gnome_copied_files)
{
manager->can_paste = TRUE;
break;
}
g_free (targets);
}
/* notify listeners that we have a new clipboard state */
g_signal_emit (manager, manager_signals[CHANGED], 0);
g_object_notify (G_OBJECT (manager), "can-paste");
/* drop the reference taken for the callback */
g_object_unref (manager);
}
static gchar *
thunar_clipboard_manager_g_file_list_to_string (GList *list,
const gchar *prefix,
gboolean format_for_text,
gsize *len)
{
GString *string;
gchar *tmp;
GList *lp;
/* allocate initial string */
string = g_string_new (prefix);
for (lp = list; lp != NULL; lp = lp->next)
{
if (format_for_text)
tmp = g_file_get_parse_name (G_FILE (lp->data));
else
tmp = g_file_get_uri (G_FILE (lp->data));
string = g_string_append (string, tmp);
g_free (tmp);
if (lp->next != NULL)
string = g_string_append_c (string, '\n');
}
if (len != NULL)
*len = string->len;
return g_string_free (string, FALSE);
}
static void
thunar_clipboard_manager_get_callback (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint target_info,
gpointer user_data)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (user_data);
GList *file_list;
gchar *str;
gchar **uris;
const gchar *prefix;
gsize len;
_thunar_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (manager->clipboard == clipboard);
/* determine the path list from the file list */
file_list = thunar_file_list_to_thunar_g_file_list (manager->files);
switch (target_info)
{
case TARGET_TEXT_URI_LIST:
uris = thunar_g_file_list_to_stringv (file_list);
gtk_selection_data_set_uris (selection_data, uris);
g_strfreev (uris);
break;
case TARGET_GNOME_COPIED_FILES:
prefix = manager->files_cutted ? "cut\n" : "copy\n";
str = thunar_clipboard_manager_g_file_list_to_string (file_list, prefix, FALSE, &len);
gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), 8, (guchar *) str, len);
g_free (str);
break;
case TARGET_UTF8_STRING:
str = thunar_clipboard_manager_g_file_list_to_string (file_list, NULL, TRUE, &len);
gtk_selection_data_set_text (selection_data, str, len);
g_free (str);
break;
default:
_thunar_assert_not_reached ();
}
/* cleanup */
thunar_g_list_free_full (file_list);
}
static void
thunar_clipboard_manager_clear_callback (GtkClipboard *clipboard,
gpointer user_data)
{
ThunarClipboardManager *manager = THUNAR_CLIPBOARD_MANAGER (user_data);
GList *lp;
_thunar_return_if_fail (GTK_IS_CLIPBOARD (clipboard));
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (manager->clipboard == clipboard);
/* release the pending files */
for (lp = manager->files; lp != NULL; lp = lp->next)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (lp->data), thunar_clipboard_manager_file_destroyed, manager);
g_object_unref (G_OBJECT (lp->data));
}
g_list_free (manager->files);
manager->files = NULL;
}
static void
thunar_clipboard_manager_transfer_files (ThunarClipboardManager *manager,
gboolean copy,
GList *files)
{
ThunarFile *file;
GList *lp;
/* release any pending files */
for (lp = manager->files; lp != NULL; lp = lp->next)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (lp->data), thunar_clipboard_manager_file_destroyed, manager);
g_object_unref (G_OBJECT (lp->data));
}
g_list_free (manager->files);
/* remember the transfer operation */
manager->files_cutted = !copy;
/* setup the new file list */
for (lp = g_list_last (files), manager->files = NULL; lp != NULL; lp = lp->prev)
{
file = THUNAR_FILE (g_object_ref (G_OBJECT (lp->data)));
manager->files = g_list_prepend (manager->files, file);
g_signal_connect (G_OBJECT (file), "destroy", G_CALLBACK (thunar_clipboard_manager_file_destroyed), manager);
}
/* acquire the CLIPBOARD ownership */
gtk_clipboard_set_with_owner (manager->clipboard, clipboard_targets,
G_N_ELEMENTS (clipboard_targets),
thunar_clipboard_manager_get_callback,
thunar_clipboard_manager_clear_callback,
G_OBJECT (manager));
/* Need to fake a "owner-change" event here if the Xserver doesn't support clipboard notification */
if (!gdk_display_supports_selection_notification (gtk_clipboard_get_display (manager->clipboard)))
thunar_clipboard_manager_owner_changed (manager->clipboard, NULL, manager);
}
/**
* thunar_clipboard_manager_get_for_display:
* @display : a #GdkDisplay.
*
* Determines the #ThunarClipboardManager that is used to manage
* the clipboard on the given @display.
*
* The caller is responsible for freeing the returned object
* using g_object_unref() when it's no longer needed.
*
* Return value: the #ThunarClipboardManager for @display.
**/
ThunarClipboardManager *
thunar_clipboard_manager_get_for_display (GdkDisplay *display)
{
ThunarClipboardManager *manager;
GtkClipboard *clipboard;
_thunar_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
/* generate the quark on-demand */
if (G_UNLIKELY (thunar_clipboard_manager_quark == 0))
thunar_clipboard_manager_quark = g_quark_from_static_string ("thunar-clipboard-manager");
/* figure out the clipboard for the given display */
clipboard = gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
/* check if a clipboard manager exists */
manager = g_object_get_qdata (G_OBJECT (clipboard), thunar_clipboard_manager_quark);
if (G_LIKELY (manager != NULL))
{
g_object_ref (G_OBJECT (manager));
return manager;
}
/* allocate a new manager */
manager = g_object_new (THUNAR_TYPE_CLIPBOARD_MANAGER, NULL);
manager->clipboard = GTK_CLIPBOARD (g_object_ref (G_OBJECT (clipboard)));
g_object_set_qdata (G_OBJECT (clipboard), thunar_clipboard_manager_quark, manager);
/* listen for the "owner-change" signal on the clipboard */
g_signal_connect (G_OBJECT (manager->clipboard), "owner-change",
G_CALLBACK (thunar_clipboard_manager_owner_changed), manager);
/* look for usable data on the clipboard */
thunar_clipboard_manager_owner_changed (manager->clipboard, NULL, manager);
return manager;
}
/**
* thunar_clipboard_manager_get_can_paste:
* @manager : a #ThunarClipboardManager.
*
* Tells whether the contents of the clipboard represented
* by @manager can be pasted into a folder.
*
* Return value: %TRUE if the contents of the clipboard
* represented by @manager can be pasted
* into a folder.
**/
gboolean
thunar_clipboard_manager_get_can_paste (ThunarClipboardManager *manager)
{
_thunar_return_val_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager), FALSE);
return manager->can_paste;
}
/**
* thunar_clipboard_manager_has_cutted_file:
* @manager : a #ThunarClipboardManager.
* @file : a #ThunarFile.
*
* Checks whether @file was cutted to the given @manager earlier.
*
* Return value: %TRUE if @file is on the cutted list of @manager.
**/
gboolean
thunar_clipboard_manager_has_cutted_file (ThunarClipboardManager *manager,
const ThunarFile *file)
{
_thunar_return_val_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager), FALSE);
_thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
return (manager->files_cutted && g_list_find (manager->files, file) != NULL);
}
/**
* thunar_clipboard_manager_copy_files:
* @manager : a #ThunarClipboardManager.
* @files : a list of #ThunarFile<!---->s.
*
* Sets the clipboard represented by @manager to
* contain the @files and marks them to be copied
* when the user pastes from the clipboard.
**/
void
thunar_clipboard_manager_copy_files (ThunarClipboardManager *manager,
GList *files)
{
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
thunar_clipboard_manager_transfer_files (manager, TRUE, files);
}
/**
* thunar_clipboard_manager_cut_files:
* @manager : a #ThunarClipboardManager.
* @files : a list of #ThunarFile<!---->s.
*
* Sets the clipboard represented by @manager to
* contain the @files and marks them to be moved
* when the user pastes from the clipboard.
**/
void
thunar_clipboard_manager_cut_files (ThunarClipboardManager *manager,
GList *files)
{
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
thunar_clipboard_manager_transfer_files (manager, FALSE, files);
}
/**
* thunar_clipboard_manager_paste_files:
* @manager : a #ThunarClipboardManager.
* @target_file : the #GFile of the folder to which the contents on the clipboard
* should be pasted.
* @widget : a #GtkWidget, on which to perform the paste or %NULL if no widget is
* known.
* @new_files_closure : a #GClosure to connect to the job's "new-files" signal,
* which will be emitted when the job finishes with the
* list of #GFile<!---->s created by the job, or
* %NULL if you're not interested in the signal.
*
* Pastes the contents from the clipboard associated with @manager to the directory
* referenced by @target_file.
**/
void
thunar_clipboard_manager_paste_files (ThunarClipboardManager *manager,
GFile *target_file,
GtkWidget *widget,
GClosure *new_files_closure)
{
ThunarClipboardPasteRequest *request;
_thunar_return_if_fail (THUNAR_IS_CLIPBOARD_MANAGER (manager));
_thunar_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
/* prepare the paste request */
request = g_slice_new0 (ThunarClipboardPasteRequest);
request->manager = THUNAR_CLIPBOARD_MANAGER (g_object_ref (G_OBJECT (manager)));
request->target_file = g_object_ref (target_file);
request->widget = widget;
/* take a reference on the closure (if any) */
if (G_LIKELY (new_files_closure != NULL))
{
request->new_files_closure = new_files_closure;
g_closure_ref (new_files_closure);
g_closure_sink (new_files_closure);
}
/* get notified when the widget is destroyed prior to
* completing the clipboard contents retrieval
*/
if (G_LIKELY (request->widget != NULL))
g_object_add_weak_pointer (G_OBJECT (request->widget), (gpointer) &request->widget);
/* schedule the request */
gtk_clipboard_request_contents (manager->clipboard, manager->x_special_gnome_copied_files,
thunar_clipboard_manager_contents_received, request);
}