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

Feature request: support for fileURL pasteboard type #9

Open
spyoungtech opened this issue Jan 27, 2021 · 8 comments
Open

Feature request: support for fileURL pasteboard type #9

spyoungtech opened this issue Jan 27, 2021 · 8 comments

Comments

@spyoungtech
Copy link

Hi there, thanks so much for making this package!

I have a request and I hope it's not too much trouble to implement. I was wondering if the fileURL pasteboardtype could be supported.

This way, it can be easy for my program to work with files that are copied from finder.

Inspecting the clipboard after copying a file from finder shows that it's exposed, among other things, as a file url type. It would be nice to be able to retrieve these contents using pasteboard

image

@tobywf
Copy link
Owner

tobywf commented Jan 27, 2021

Depending on what Python type the output should be, it shouldn't be too hard. str or bytes? How do I test/trigger a paste with a file URL?

@spyoungtech
Copy link
Author

spyoungtech commented Jan 27, 2021

Hmm. str should be fine, since the contents should only include filesystem paths, which shouldn't include any non-unicode characters, as far as I know.

I would imagine the interface would look something like:

# Set a file to the clipboard
pb.set_contents('file:///Users/path/to/file.ext', pasteboard.FileURL)

# Get the contents
pb.get_contents(pasteboard.FileURL)
# 'file:///Users/path/to/file.ext'

How do I test/trigger a paste with a file URL?

I'm not sure I totally understand the question. I think it's sufficient to just test/return the clipboard contents, which should just be the file path in the case of a fileURL type. Should work pretty much the same as a unicode string. I don't think there's anything special or extravagant that needs to be accounted for.

Although, I'm not 100% sure what typical programs look for when pasting files, if you mean test it in that way. When I copy from finder, as shown in the image above, Apple decides to place many representations at the same time, including public.file-url, NSFileNamesPboardType, utf-8 plaintext, ut16, and more.

I'm guessing that the presence of representation of public.file-url or NSFileNamesPboardType would allow you to paste into Finder or similar applications.

@tobywf
Copy link
Owner

tobywf commented Jan 28, 2021

When I copy from finder, as shown in the image above

Perfect, sorry, that what I meant by "How do I test/trigger a paste with a file URL?". I should've said "what steps I'd have to do so the pasteboard is populated with an entry of public.file-url, so i can check it actually works with a real-world example, and write a test that's kinda close-ish." Cheers! Might have time to do it this week, or you're welcome to cut a PR.

@tobywf
Copy link
Owner

tobywf commented Jan 29, 2021

The latest release has support for FileURLs (it's up on PyPI).

@spyoungtech
Copy link
Author

Thanks much for this @tobywf

Can confirm the get_file_urls() method is working. Awesome!

I was wondering: is it possible to set this format on the clipboard?

@tobywf
Copy link
Owner

tobywf commented Jan 30, 2021

I'm sure it is possible, but it might need some design/thought/testing. I'm not well-versed in macOS/AppKit API limitations, and the API documentation is shockingly barren, e.g. writeObjects.

I guess the first question would be "Why?". From a bit of testing when I was implementing get, it does seem to be sufficient to set public.file-url for e.g. the Finder to be able to paste it. Other programs might not be so forgiving. This didn't work very smoothly though for string types (as I outlined in #10, it made my Finder hang for several seconds(!)).

Which brings us to the technical side. Do the FileURLs have to exist, and do bad things happen if they don't? Presumably not, because you could delete a file after setting the value. I do know (by accident) you can only set absolute FileURLs, which makes sense if you think about it, but couldn't find it mentioned anywhere. I'm honestly not sure what/how much validation is done by either (NSURL)fileURLWithPath or (NSPasteboard)writeObjects.

It might be a good idea for get_file_urls() to return a sequence of pathlib.Paths, and equally, set_file_urls() would take such a sequence. Probably even more specifically, PosixPath instances. Then, e.g. is_absolute() or as_uri() could be called to do some validation on the Python side already, although I'd have to figure out how to call those methods in C - I don't use the Python C API often any more.

I might have a play around with this when I have time, but help answering these questions would be appreciated. The other alternative is for you to use PyObjC if you have a specific use-case and want more direct control.

@spyoungtech
Copy link
Author

spyoungtech commented Jan 30, 2021

the API documentation is shockingly barren

Yeah, tell me about it 😞 -- I've been struggling for a couple weeks to find good examples of interacting with the pasteboard, and the closest I've found is some antiquated example code for an application called ClipboardViewer which uses deprecated APIs.

I guess the first question would be "Why?"

I'm looking for this capability for a program I'm writing that interacts with the clipboard across platforms. One feature is the ability to copy/paste files to/from the clipboard from a CLI or Python interface and that action should be compatible with graphical file explorers (or other programs that accept the file Clipboard format), like explorer on Windows or Finder on MacOS.

Admittedly, I think the most common case would be reading this after somebody copies it, which is covered in the latest release. But I would like to have bidirectional, which I'm able to do so far in Windows and Linux. The ecosystem for MacOS is sparse and, unfortunately, I've not touched Objective-C until a few weeks ago :-)

It might be a good idea for get_file_urls() to return a sequence of pathlib.Paths, and equally, set_file_urls() would take such a sequence.

Yeah, pathlib.Path is a good interface. For input parameters, I believe they should be substitutable for strings, since strings are, generally, accepted anywhere pathlib.Path is accepted.

although I'd have to figure out how to call those methods in C

I wonder if that's really necessary though. My thought is that you could implement it in Python.

As I understand it, pasteboard exposes the interfaces from the compiled extension directly in its public interface. In my mind, you could add another layer in between the extension and public interface.

For example, in __init__.py we see:

from ._native import *

Instead, you could have this code in, say, a _pasteboard.py file then encapsulate the extension in a Python class that is exposed in __init__.py instead, where you could do such validation and interactions with pathlib.Path.

For example, maybe something like this:

from pasteboard import Pasteboard as _Pasteboard # or from _pasteboard internally to this project ;)
from typing import Union, Sequence, Optional
import pathlib
class Pasteboard:
    def __init__(self, *args, **kwargs):
        self._pb = _Pasteboard(*args, **kwargs)  
    ...
    def get_file_urls(self, diff: bool=False):
        files = self._pb.get_file_urls(diff)
        if files:
            return tuple(pathlib.Path(f) for f in files)
        return tuple()  # a gentle suggestion :-)

    def set_file_urls(self, paths: Sequence[Union[str, pathlib.Path]]) -> bool:
        urls = tuple(pathlib.Path(p).as_uri() for p in paths)
        ...  # do more validations?
        self._pb.set_file_urls(urls)

Subclassing could also work cleanly, I think.

@tobywf
Copy link
Owner

tobywf commented Feb 5, 2021

I dislike the split Python/native approach, having to wrap everything is meh. I guess inheriting from the (C) object might be possible?! Maybe that kind of protection isn't needed and I was overthinking it. Consumers could try and write what they want; it'll just fail.

I hit a bigger problem though. Ostensibly you're supposed to use writeObjects to write multiple values. Unfortunately, pastes seem to block horribly in Python by default (until the interpreter quits), and I was able to reproduce it with a minimal example of pure ObjC with a blocking getchar. I also tried the older, deprecated NSFilenamesPboardType/declareTypes + setPropertyList, same effect. I'm not sure why, but I suspect that using AppKit functions without a window is questionable at best. One avenue might be spawning a thread, or dispatch_async, but at some point, we probably just need someone with actual macOS development knowledge. At this point though, I'm going to give up for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants