- Implemented detection of clipboard content types (text, files, and images). - Added support for pasting file paths and temporary image paths. - Ensured plain text pasting remains compatible with security checks.
224 lines
7.3 KiB
Diff
224 lines
7.3 KiB
Diff
From 366641e9188df09d7bef6c091e916d277ddc37a1 Mon Sep 17 00:00:00 2001
|
||
From: Harald Hoyer <harald@hoyer.xyz>
|
||
Date: Fri, 4 Jul 2025 14:47:45 +0200
|
||
Subject: [PATCH] terminal: Add image and file path pasting support for
|
||
Shift+Ctrl+V
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
Enhance the paste functionality to detect different clipboard content types:
|
||
- File URIs are pasted as shell-quoted file paths
|
||
- Images are saved to temporary files and their paths are pasted
|
||
- Plain text continues to work as before with security checks
|
||
|
||
This provides a consistent experience with the drag-and-drop functionality
|
||
and allows users to easily paste file paths from file managers or
|
||
temporary paths for copied images.
|
||
|
||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||
|
||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
---
|
||
src/kgx-terminal.c | 160 +++++++++++++++++++++++++++++++++++++++++++--
|
||
1 file changed, 155 insertions(+), 5 deletions(-)
|
||
|
||
diff --git a/src/kgx-terminal.c b/src/kgx-terminal.c
|
||
index fffbb92..fd9f936 100644
|
||
--- a/src/kgx-terminal.c
|
||
+++ b/src/kgx-terminal.c
|
||
@@ -28,10 +28,13 @@
|
||
#include "kgx-config.h"
|
||
|
||
#include <glib/gi18n.h>
|
||
+#include <glib/gstdio.h>
|
||
+#include <unistd.h>
|
||
|
||
#include <vte/vte.h>
|
||
#define PCRE2_CODE_UNIT_WIDTH 0
|
||
#include <pcre2.h>
|
||
+#include <gdk-pixbuf/gdk-pixbuf.h>
|
||
|
||
#include "kgx-despatcher.h"
|
||
#include "kgx-marshals.h"
|
||
@@ -41,6 +44,16 @@
|
||
|
||
#include "kgx-terminal.h"
|
||
|
||
+/* Translators: The user ctrl-clicked, or used ‘Open Link’, on a URI that,
|
||
+ * for whatever reason, we were unable to open. */
|
||
+#define URI_FAILED_MESSAGE C_("toast-message", "Couldn't Open Link")
|
||
+
|
||
+/* MIME types for clipboard content detection */
|
||
+#define MIME_TEXT_URI_LIST "text/uri-list"
|
||
+#define MIME_TEXT_PLAIN "text/plain;charset=utf-8"
|
||
+#define MIME_PORTAL_FILES "application/vnd.portal.filetransfer"
|
||
+#define MIME_PORTAL_FILES_OLD "application/vnd.portal.files"
|
||
+
|
||
/* Regex adapted from TerminalWidget.vala in Pantheon Terminal */
|
||
|
||
#define USERCHARS "-[:alnum:]"
|
||
@@ -365,6 +378,110 @@ copy_activated (KgxTerminal *self)
|
||
}
|
||
|
||
|
||
+static char *
|
||
+quote_file_path (GFile *file)
|
||
+{
|
||
+ g_autofree char *path = g_file_get_path (file);
|
||
+
|
||
+ if (G_LIKELY (path)) {
|
||
+ return g_shell_quote (path);
|
||
+ } else {
|
||
+ /* Fall back to URI if path extraction fails */
|
||
+ g_autofree char *uri = g_file_get_uri (file);
|
||
+ return g_strdup (uri);
|
||
+ }
|
||
+}
|
||
+
|
||
+
|
||
+static void
|
||
+got_file_list (GObject *source,
|
||
+ GAsyncResult *result,
|
||
+ gpointer user_data)
|
||
+{
|
||
+ g_autoptr (KgxTerminal) self = user_data;
|
||
+ g_autoptr (GError) error = NULL;
|
||
+ g_autoptr (GStrvBuilder) builder = NULL;
|
||
+ g_auto (GStrv) items = NULL;
|
||
+ g_autofree char *text = NULL;
|
||
+ const GValue *value;
|
||
+ GSList *files;
|
||
+
|
||
+ /* Get the resulting file list */
|
||
+ value = gdk_clipboard_read_value_finish (GDK_CLIPBOARD (source), result, &error);
|
||
+
|
||
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||
+ return;
|
||
+ } else if (error) {
|
||
+ g_critical ("Couldn't paste files: %s\n", error->message);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ builder = g_strv_builder_new ();
|
||
+ files = g_value_get_boxed (value);
|
||
+
|
||
+ for (GSList *l = files; l; l = g_slist_next (l)) {
|
||
+ g_autofree char *quoted = quote_file_path (G_FILE (l->data));
|
||
+ g_strv_builder_add (builder, quoted);
|
||
+ }
|
||
+
|
||
+ /* Add empty string to facilitate consecutive pastes */
|
||
+ g_strv_builder_add (builder, "");
|
||
+ items = g_strv_builder_end (builder);
|
||
+ text = g_strjoinv (" ", items);
|
||
+
|
||
+ kgx_terminal_accept_paste (self, text);
|
||
+}
|
||
+
|
||
+
|
||
+static void
|
||
+got_image (GObject *source,
|
||
+ GAsyncResult *result,
|
||
+ gpointer user_data)
|
||
+{
|
||
+ g_autoptr (KgxTerminal) self = user_data;
|
||
+ g_autoptr (GError) error = NULL;
|
||
+ g_autoptr (GdkTexture) texture = NULL;
|
||
+ g_autofree char *text = NULL;
|
||
+ g_autofree char *temp_path = NULL;
|
||
+ g_autofree char *quoted_path = NULL;
|
||
+ GBytes *bytes;
|
||
+ int fd;
|
||
+
|
||
+ /* Get the resulting texture */
|
||
+ texture = gdk_clipboard_read_texture_finish (GDK_CLIPBOARD (source), result, &error);
|
||
+
|
||
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||
+ return;
|
||
+ } else if (error) {
|
||
+ g_critical ("Couldn't paste image: %s\n", error->message);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ /* Create a temporary file for the image */
|
||
+ fd = g_file_open_tmp ("kgx-clipboard-XXXXXX.png", &temp_path, &error);
|
||
+ if (fd == -1) {
|
||
+ g_critical ("Failed to create temporary file for clipboard image: %s\n", error->message);
|
||
+ return;
|
||
+ }
|
||
+ close (fd);
|
||
+
|
||
+ /* Save texture data directly */
|
||
+ bytes = gdk_texture_save_to_png_bytes (texture);
|
||
+ if (!g_file_set_contents (temp_path, g_bytes_get_data (bytes, NULL),
|
||
+ g_bytes_get_size (bytes), &error)) {
|
||
+ g_bytes_unref (bytes);
|
||
+ g_unlink (temp_path);
|
||
+ g_critical ("Failed to save clipboard image to temporary file: %s\n", error->message);
|
||
+ return;
|
||
+ }
|
||
+ g_bytes_unref (bytes);
|
||
+
|
||
+ /* Quote the path and paste it */
|
||
+ quoted_path = g_shell_quote (temp_path);
|
||
+ kgx_terminal_accept_paste (self, quoted_path);
|
||
+}
|
||
+
|
||
+
|
||
static void
|
||
got_text (GObject *source,
|
||
GAsyncResult *result,
|
||
@@ -392,11 +509,44 @@ static void
|
||
paste_activated (KgxTerminal *self)
|
||
{
|
||
GdkClipboard *cb = gtk_widget_get_clipboard (GTK_WIDGET (self));
|
||
-
|
||
- gdk_clipboard_read_text_async (cb,
|
||
- self->cancellable,
|
||
- got_text,
|
||
- g_object_ref (self));
|
||
+ GdkContentFormats *formats = gdk_clipboard_get_formats (cb);
|
||
+ const char *const *mimes = gdk_content_formats_get_mime_types (formats, NULL);
|
||
+
|
||
+ /* Check clipboard content types in priority order */
|
||
+ if (G_LIKELY (g_strv_contains (mimes, MIME_PORTAL_FILES)) ||
|
||
+ g_strv_contains (mimes, MIME_PORTAL_FILES_OLD)) {
|
||
+ /* File list from modern applications */
|
||
+ gdk_clipboard_read_value_async (cb,
|
||
+ GDK_TYPE_FILE_LIST,
|
||
+ G_PRIORITY_DEFAULT,
|
||
+ self->cancellable,
|
||
+ got_file_list,
|
||
+ g_object_ref (self));
|
||
+ } else if (g_strv_contains (mimes, MIME_TEXT_URI_LIST)) {
|
||
+ /* URI list from older applications */
|
||
+ gdk_clipboard_read_value_async (cb,
|
||
+ GDK_TYPE_FILE_LIST,
|
||
+ G_PRIORITY_DEFAULT,
|
||
+ self->cancellable,
|
||
+ got_file_list,
|
||
+ g_object_ref (self));
|
||
+ } else if (g_strv_contains (mimes, "image/png") ||
|
||
+ g_strv_contains (mimes, "image/jpeg") ||
|
||
+ g_strv_contains (mimes, "image/gif") ||
|
||
+ g_strv_contains (mimes, "image/bmp") ||
|
||
+ g_strv_contains (mimes, "image/webp")) {
|
||
+ /* Image data - save to temp file */
|
||
+ gdk_clipboard_read_texture_async (cb,
|
||
+ self->cancellable,
|
||
+ got_image,
|
||
+ g_object_ref (self));
|
||
+ } else {
|
||
+ /* Default: try to read as text */
|
||
+ gdk_clipboard_read_text_async (cb,
|
||
+ self->cancellable,
|
||
+ got_text,
|
||
+ g_object_ref (self));
|
||
+ }
|
||
}
|
||
|
||
|
||
--
|
||
2.49.0
|
||
|