Skip to content

Conversation

@Speykious
Copy link
Contributor

@Speykious Speykious commented Apr 13, 2024

This PR supersedes #6151 which aimed at adding support for copying and pasting images from the clipboard on Linux, but had the wrong approach of calling into an external executable (xclip for X11).

In the mean time I had actually started a native clipboard library in Rust for applications that would support any mime types and be given arbitrary data (the X11 implementation was somewhat finished, but not Wayland), but since osu!framework now uses SDL3, I can just use SDL_GetClipboardData and SDL_SetClipboardData as suggested back then by @Susko3.

This is a first attempt at implementing this, and on my Linux machine, the visual tests for copying images are all green! :D
Edit: actually doesn't work every time! D: I'll have to really refactor this code and make sure I understand what's going on.
Edit 2: bug identified, it seems to come from the given image itself...

That said, I really don't know what to think of this code, [...] I think the code looks ok now :)

@Speykious
Copy link
Contributor Author

There's also the caveat that I only implemented a way to copy PNGs. Wouldn't it also need to be able to copy JPGs depending on some setting? I know osu! lets you choose the image format for screenshot savings.

@smoogipoo
Copy link
Contributor

What you have looks pretty standard for the new age of unmanaged->managed callbacks. That being said, this PR now competes with #6223 and we should probably make sure that this implementation is going to easily lend itself to other mime types in the future.

unsafe
{
nuint size = 0;
byte* data = (byte*)SDL3.SDL_GetClipboardData(Encoding.UTF8.GetBytes("image/png"), &size);
Copy link
Contributor

@FreezyLemon FreezyLemon Apr 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe SDL3-CS handles this automagically somehow, but I think you're missing a null terminator here (Pretty sure the marshaller only does this for string-typed pinvoke-parameters). The byte[] returned by Encoding.GetBytes does not include a null byte at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would "image/png\0"u8 work better then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that should work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@Speykious
Copy link
Contributor Author

image
I debugged a lot today (both with a debugger and with prints) and concluded that pngData was fine as a static due to how the API works. It only calls for a cleanup once if we're requesting for an image again.

Pass unmanaged struct to callbacks and free it on cleanup
@Speykious
Copy link
Contributor Author

image
It seems that sometimes the test gives a completely empty screenshot. I was worried that my implementation had some weird undefined behavior thing, but it seems like that bug is happening elsewhere...?

@Speykious Speykious marked this pull request as ready for review April 14, 2024 11:19
@Speykious
Copy link
Contributor Author

Speykious commented Apr 14, 2024

image
It seems I'm missing something related to the unmanaged call convention
Solved, it was because I was using C#'s collection expression syntax... oh well :/

@Susko3 Susko3 self-requested a review April 15, 2024 08:13
@Susko3
Copy link
Member

Susko3 commented Apr 15, 2024

I'm testing this on windows (by commenting out the WindowsClipboard override) and I can't get images from osu!framework into external programs (and vice versa). The clipboard tests pass fine, but applications complain about there being no image in the clipboard. And trying to paste images into framework doesn't work: either I get the stale image (last one copied from framework) or no image at all (even when copying a sample PNG from the internet).

image

Does copying images to/from framework into external programs work on Linux?

@Speykious
Copy link
Contributor Author

It does work on Linux (at least on my machine on X11), as long as there is a clipboard manager present. If there is no clipboard manager, the selection will disappear once the application closes, which is expected behavior.

I don't know why it doesn't work on Windows though. :/

@Susko3
Copy link
Member

Susko3 commented Apr 15, 2024

Works perfectly fine on windows if the mime type is changed to image/bmp and the image is saved with .SaveAsBmp(). Do bitmaps work on Linux too?

I wonder if it works on macOS (which currently uses MacOSClipboard) and iOS (which uses SDL3Clipboard).

@FreezyLemon
Copy link
Contributor

Works perfectly fine on windows if the mime type is changed to image/bmp and the image is saved with .SaveAsBmp(). Do bitmaps work on Linux too?

I wonder if it works on macOS (which currently uses MacOSClipboard) and iOS (which uses SDL3Clipboard).

At least on Wayland, the mime type also has to match. If I e.g. copy a PNG from my local storage, the current code works fine. If I use a Paint-like program to scribble around, Ctrl-A and Ctrl-C, then it's also saved as an image/bmp and needs that exact mime type to be retrieved from SDL.

The obvious solution would be to just have a list of image mimetypes that are tried one after another. FWIW, SDL itself also does this for the built-in GetClipboardText function.

@Speykious
Copy link
Contributor Author

I'll try to implement a multi-mime-types solution.

@Susko3
Copy link
Member

Susko3 commented Apr 15, 2024

We should probably ask ImageSharp about its supported decoder formats, see SixLabors/ImageSharp#1353 (but with IImageFormat.MimeTypes).

This is what I get on windows:

image/png
image/apng
image/jpeg
image/pjpeg
image/gif
image/bmp
image/x-windows-bmp
image/x-portable-pixmap
image/x-portable-anymap
image/x-tga
image/x-targa
image/tiff
image/tiff-fx
image/webp
image/qoi
image/x-qoi
image/vnd.qoi

Copy link
Member

@Susko3 Susko3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather rewrite this PR then to try and fix it.

Comment on lines 34 to 41
nuint size = 0;
byte* data = (byte*)SDL3.SDL_GetClipboardData("image/png\0"u8, &size);
if (data == null)
return null;

buffer = new byte[size];
for (nuint i = 0; i < size; i++)
buffer[i] = data[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use Marshal.Copy.

Comment on lines 49 to 53
private unsafe struct ClipboardImageData
{
public byte* RawBuffer;
public nuint Length;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably be done much cleaner with a GCHandleType.Pinned ObjectHandle<byte[]>.

Similar to:

using (var handle = new ObjectHandle<byte[]>(data, GCHandleType.Pinned))
SampleId = Bass.SampleLoad(handle.Address, 0, dataLength, PlaybackConcurrency.Value, flags);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing around a byte[] means the copying will happen in the callback and not before.
But I might end up using an ObjectHandle anyways to support multiple mime types. Only this time it'll need to be the Image itself.

Copy link
Contributor Author

@Speykious Speykious Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is trickier than I expected.
In the SampleBassFactory code, the handle is created with using, so it gets freed immediately after SampleLoad gets called. This is fine because SampleLoad makes a copy of the data immediately - from its documentation:

There is no need to pin the memory buffer for this method, since after loading a sample from memory, the memory can safely be discarded, as a copy is made.

But I can't just free the ObjectHandle immediately after SDL_SetClipboardData gets called. Instead I have to free it inside of the cleanup callback, which will be called with the old userdata before each new call to SDL_SetClipboardData.
And unfortunately, I can't just do it manually with .Dispose() either, because disposing an object handle obtained from a nint won't actually free the object handle, we can only free it on the original one.

I feel like now I need to have two swapping global variables (due to how the cleanup logic works), which would make an object handle unnecessary...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like now I need to have two swapping global variables

Welp, this is what I have currently

Comment on lines 86 to 88
fixed (byte* pngMimetypeStr = "image/png\0"u8)
{
if (SDL3.SDL_strcmp(mimeType, pngMimetypeStr) == 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason to delegate string comparison to SDL. Better to decode the pointer with SDL3.PtrToStringUTF8 and use == on C# strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using SDL_strcmp, I don't need to copy any data over before doing the comparison.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up doing it anyway to be able to use ImageFormatManager.TryFindFormatByMimeType :)

@Speykious
Copy link
Contributor Author

Works perfectly fine on windows if the mime type is changed to image/bmp and the image is saved with .SaveAsBmp().

@Susko3 Can you tell me if this works on Windows now?

@Speykious
Copy link
Contributor Author

In any case, I can confirm that it works on MacOS.
image
image

@Susko3
Copy link
Member

Susko3 commented Apr 19, 2024

Thanks for the PR, I've improved on your idea and made it into #6254.

Please give it a test and see if the BMP format works for you. If not, I can change it to PNG.

@Susko3 Susko3 closed this Apr 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants