feat(gnome-console): add image and file path pasting support
- 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.
This commit is contained in:
parent
b23f555241
commit
45cb8db57f
2 changed files with 232 additions and 0 deletions
8
overlays/mods/default.nix
Normal file
8
overlays/mods/default.nix
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{ ... }:
|
||||||
|
final: prev: {
|
||||||
|
gnome-console = prev.gnome-console.overrideAttrs (prevAttrs: {
|
||||||
|
patches = (prevAttrs.patches or [ ]) ++ [
|
||||||
|
./gnome-console-Add-image-and-file-path-pasting-support-for.patch
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue