From 45cb8db57f6df23284d9b2d56627ea42c0a34fdf Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 4 Jul 2025 16:47:17 +0200 Subject: [PATCH] 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. --- overlays/mods/default.nix | 8 + ...ge-and-file-path-pasting-support-for.patch | 224 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 overlays/mods/default.nix create mode 100644 overlays/mods/gnome-console-Add-image-and-file-path-pasting-support-for.patch diff --git a/overlays/mods/default.nix b/overlays/mods/default.nix new file mode 100644 index 0000000..c294667 --- /dev/null +++ b/overlays/mods/default.nix @@ -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 + ]; + }); +} diff --git a/overlays/mods/gnome-console-Add-image-and-file-path-pasting-support-for.patch b/overlays/mods/gnome-console-Add-image-and-file-path-pasting-support-for.patch new file mode 100644 index 0000000..c7f7b99 --- /dev/null +++ b/overlays/mods/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 +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 +--- + 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 ++#include ++#include + + #include + #define PCRE2_CODE_UNIT_WIDTH 0 + #include ++#include + + #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 +