Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use file transfer to support copy and paste of files #3329

Open
totaam opened this issue Oct 27, 2021 · 2 comments
Open

use file transfer to support copy and paste of files #3329

totaam opened this issue Oct 27, 2021 · 2 comments
Labels
clipboard enhancement New feature or request

Comments

@totaam
Copy link
Collaborator

totaam commented Oct 27, 2021

So we can select a file in the file manager and paste it in the local one.

For gnome, this means intercepting x-special/gnome-copied-files and sending the file via the file-transfer feature, then if the client accepts the transfer then we can reply with a path to the file we have transferred over.
This may be a problem and trigger timeouts... perhaps the file transfer feature will need a new option to allow clipboard transfers without waiting for confirmation?

@totaam
Copy link
Collaborator Author

totaam commented Oct 27, 2021

Here's what happens when copying a file (summary) at present:

send_clipboard_token_handler CLIPBOARD to CLIPBOARD
client   9 @50.545 claim_selection: done, owned=True
client   9 @50.547 clipboard request for CLIPBOARD from window 0x60000f: 0x0, target=TARGETS, prop=META_SELECTION_0x563745a734c0
requesting local XConvertSelection from 'org.gnome.Nautilus' as 'TARGETS' into 'CLIPBOARD-TARGETS'
client   9 @50.549 clipboard request for CLIPBOARD from window 0x2c00130: 'gedit', target=TARGETS, prop=GDK_SELECTION
CLIPBOARD-TARGETS=b'\xee\x01\x00\x00\x00\x00\x00\x00\x0b\x02\x00\x .. x00\x00\x00\x00\xa3\x01\x00\x00\x00\x00\x00\x00' (ATOM : 32)
_filter_targets(TIMESTAMP, TARGETS, MULTIPLE, x-special/gnome-copied-files, text/uri-list, UTF8_STRING, COMPOUND_TEXT, TEXT, STRING, text/plain;charset=utf-8, text/plain)=('TIMESTAMP', 'TARGETS', 'MULTIPLE', 'text/uri-list', 'UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING', 'text/plain;charset=utf-8', 'text/plain')
client   9 @50.550 clipboard request for CLIPBOARD from window 0x1c00010: 'clipit', target=text/plain;charset=utf-8, prop=GDK_SELECTION
client   9 @50.552 clipboard request for CLIPBOARD from window 0x6800358: 'org.gnome.Nautilus', target=TARGETS, prop=GDK_SELECTION
client   9 @50.552 clipboard request for CLIPBOARD from window 0x6815e6d: 'org.gnome.Nautilus', target=x-special/gnome-copied-files, prop=GDK_SELECTION
client   9 @50.552 clipboard CLIPBOARD rejecting request for invalid target 'x-special/gnome-copied-files'
client   9 @50.553  coming from 'org.gnome.Nautilus'
client   9 @50.560 setting response b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00' as 'TARGETS' on property 'META_SELECTION_0x563745a734c0' of window 0x0 as ATOM
client   9 @50.560 set_selection_response(<GdkX11.X11Window object at 0x7fc0900ced80 (GdkX11Window at 0x5609a37c3dd0)>, TARGETS, META_SELECTION_0x563745a734c0, ATOM, 32, b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00', 72181863)
2021-10-27 19:43:33,117 client   9 @50.561 setting response b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00' as 'TARGETS' on property 'GDK_SELECTION' of window 'gedit' as ATOM
client   9 @50.561 set_selection_response(<GdkX11.X11Window object at 0x7fc108b7f8c0 (GdkX11Window at 0x5609a21bb030)>, TARGETS, GDK_SELECTION, ATOM, 32, b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00', 72181863)
2021-10-27 19:43:33,118 client   9 @50.561 setting response b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00' as 'TARGETS' on property 'GDK_SELECTION' of window 'Terminal' as ATOM
client   9 @50.561 set_selection_response(<GdkX11.X11Window object at 0x7fc108c6c940 (GdkX11Window at 0x5609a21bb1d0)>, TARGETS, GDK_SELECTION, ATOM, 32, b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00', 72181863)
client   9 @50.561 setting response b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00' as 'TARGETS' on property 'GDK_SELECTION' of window 'org.gnome.Nautilus' as ATOM
client   9 @50.562 set_selection_response(<GdkX11.X11Window object at 0x7fc0900cebc0 (GdkX11Window at 0x5609a220bc20)>, TARGETS, GDK_SELECTION, ATOM, 32, b'\xa1\x01\x00\x00\x00\x00\x00\x00\x9f\x01\x00\x .. 00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x00\x00', 72181864)
got_local_contents: calling <function ClipboardProtocolHelperCore._process_clipboard_request.<locals>.got_contents at 0x7fc8e3362b80>('UTF8_STRING', 8, b'/home/antoine/xeyes.png')
client   9 @50.567 setting response b'/home/antoine/xeyes.png' as 'UTF8_STRING' on property 'META_SELECTION_0x563745a73410' of window 0x0 as UTF8_STRING

We currently discard x-special/gnome-copied-files from the TARGETS since files aren't handled yet.
Despite that, Nautilus still requests it.. And we give it an empty response.


On MS Windows, we're meant to use CF_HDROP: Read different types of clipboard data with ctypes in python

@totaam totaam transferred this issue from Xpra-org/xpra-html5 Oct 27, 2021
totaam added a commit that referenced this issue Oct 27, 2021
and use a dedicate method for each direction
@totaam
Copy link
Collaborator Author

totaam commented Oct 27, 2021

The big hurdle is how so many applications greedily request the file URIs.
To respond, we would need to transfer the files. All just in case the remote application does actually use any of the URIs.

Here's a patch that starts adding support for file targets by translating the gnome / mate specific targets to a generic name we can then handle ourselves:

diff --git a/xpra/clipboard/clipboard_core.py b/xpra/clipboard/clipboard_core.py
index 303316670..a0afe423f 100644
--- a/xpra/clipboard/clipboard_core.py
+++ b/xpra/clipboard/clipboard_core.py
@@ -48,7 +48,6 @@ DISCARD_TARGETS = tuple(re.compile(dt) for dt in get_discard_targets("DISCARD",
     r"^CorePasteboardFlavorType",
     r"^dyn\.",
     r"^resource-transfer-format",           #eclipse
-    r"^x-special/",                         #ie: gnome file copy
     )))
 #targets some applications are known to request,
 #even when the peer did not expose them as valid targets,
@@ -62,6 +61,7 @@ DISCARD_EXTRA_TARGETS = tuple(re.compile(dt) for dt in get_discard_targets("DISC
 log("DISCARD_TARGETS=%s", csv(DISCARD_TARGETS))
 log("DISCARD_EXTRA_TARGETS=%s", csv(DISCARD_EXTRA_TARGETS))
 
+FILE_TARGET = "x-special/xpra-copied-file"
 
 TEXT_TARGETS = ("UTF8_STRING", "TEXT", "STRING", "text/plain")
 
@@ -84,7 +84,7 @@ def must_discard_extra(target):
 
 def _filter_targets(targets):
     targets_strs = tuple(bytestostr(x) for x in targets)
-    f = tuple(target for target in targets_strs if not must_discard(target))
+    f = [target for target in targets_strs if not must_discard(target)]
     log("_filter_targets(%s)=%s", csv(targets_strs), f)
     return f
 
diff --git a/xpra/x11/gtk_x11/clipboard.py b/xpra/x11/gtk_x11/clipboard.py
index 85285645b..2d2219c2e 100644
--- a/xpra/x11/gtk_x11/clipboard.py
+++ b/xpra/x11/gtk_x11/clipboard.py
@@ -19,7 +19,7 @@ from xpra.x11.gtk_x11.gdk_bindings import (
     )
 from xpra.gtk_common.error import XError
 from xpra.clipboard.clipboard_core import (
-    ClipboardProxyCore, TEXT_TARGETS,
+    ClipboardProxyCore, TEXT_TARGETS, FILE_TARGET,
     must_discard, must_discard_extra, _filter_targets,
     )
 from xpra.clipboard.clipboard_timeout_helper import ClipboardTimeoutHelper, CONVERT_TIMEOUT
@@ -68,6 +68,8 @@ DEFAULT_TRANSLATED_TARGETS = "#".join((
 TRANSLATED_TARGETS = parse_translated_targets(os.environ.get("XPRA_CLIPBOARD_TRANSLATED_TARGETS", DEFAULT_TRANSLATED_TARGETS))
 log("TRANSLATED_TARGETS=%s", TRANSLATED_TARGETS)
 
+FILE_TARGETS = ("x-special/gnome-copied-files", "x-special/mate-copied-files")
+
 
 def xatoms_to_strings(data):
     l = len(data)
@@ -209,6 +211,31 @@ class X11Clipboard(ClipboardTimeoutHelper, GObject.GObject):
             return strings_to_xatoms(self.local_targets(data))
         return super()._munge_wire_selection_to_raw(encoding, dtype, dformat, data)
 
+
+    def local_targets(self, remote_targets):
+        targets = super().local_targets(remote_targets)
+        try:
+            targets.remove(FILE_TARGET)
+        except ValueError:
+            pass
+        else:
+            targets += FILE_TARGETS
+        log.warn("local_targets(%s)=%s", remote_targets, targets)
+        return targets
+
+    def remote_targets(self, local_targets):
+        targets = super().remote_targets(local_targets)
+        for x in FILE_TARGETS:
+            try:
+                targets.remove(x)
+            except ValueError:
+                pass
+            else:
+                if FILE_TARGET not in targets:
+                    targets.append(FILE_TARGET)
+        log.warn("remote_targets(%s)=%s", local_targets, targets)
+        return targets
+
 GObject.type_register(X11Clipboard)
 
 
@@ -260,7 +287,6 @@ class ClipboardProxy(ClipboardProxyCore, GObject.GObject):
         for target in lr:
             self.got_local_contents(target)
 
-
     def got_token(self, targets, target_data=None, claim=True, synchronous_client=False):
         # the remote end now owns the clipboard
         self.cancel_emit_token()
@@ -351,7 +377,7 @@ class ClipboardProxy(ClipboardProxyCore, GObject.GObject):
     # forward local requests to the remote clipboard:
     ############################################################################
     def do_selection_request_event(self, event):
-        #an app is requesting clipboard data from us
+        #a local app is requesting clipboard data from us
         log("do_selection_request_event(%s)", event)
         requestor = event.requestor
         if not requestor:
@@ -415,6 +441,8 @@ class ClipboardProxy(ClipboardProxyCore, GObject.GObject):
             return
 
         req_target = target
+        if target in FILE_TARGETS:
+            pass
         if self.targets and target not in self.targets:
             if first_time("client-%s-invalidtarget-%s" % (wininfo, target)):
                 l = log.info

One way to solve this without transferring the files whenever they are copied onto the clipboard would be to use python-fuse to create a temporary holding area.
The client applications would be able to "see" the file and query its attributes (size, etc) and then we would be able to request the transfer when they try to read it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clipboard enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant