Status of clipboard support in SDL3

Hi,

at FreeRDP there have been some attempts to have a SDL3 based client. In the tests I’ve done, it seems like the clipboard is not working very well, at least with last SDL master I don’t get notified with SDL events when some content is copied to the clipboard (that’s under Ubuntu 22.04 and X11/Kwin).

What is the status of the clipboard support in SDL3 ? Is it working perfectly and we’re doing something wrong in our FreeRDP client, or is it possible that it’s not stable yet (at least with X11) ?

Best regards.

On Debian 11 with MATE (X11) I do receive SDL_EVENT_CLIPBOARD_UPDATE when the clipboard changes.

The issues I experience are:

  • If I use SDL_SetClipboardText it will not be available to other programs after I have closed my program.
  • After I have used SDL_SetClipboardText then SDL_HasClipboardData will always return true and SDL_GetClipboardData will return a pointer to the clipboard text no matter which mime type I specify.
  • If I use another program to copy some “data” (e.g. by selecting part of an image in GIMP and pressing Ctrl+C) while my program is running then the clipboard text will still report the old value.

Here’s some things that I think I can help with.

Many Linux versions out of the box do not come with a clipboard manager. (Examples: Fedora and Ubuntu, though it looks like Kubuntu does have one)

This is kind of how I imagine clipboard works in these Linux: the window sending the data tells the OS that the program has been told to share clipboard data and the program hands over a hook/handle (the callback that actually serves the data) for the OS to hold onto.
When the receiver requests data of a certain mime-type, the sender’s callback packages the data up in that requested mime-type and sends it.
If the sender gets closed, then the callback is nullified. There is nobody to request information from, the data can’t be served to the pipeline, and nothing that gets sent.

A clipboard manager works by requesting and storing the data in multiple mime types and then makes the same request to the OS for control of the clipboard pipeline. The results are available for any receiver to request at a later time, even if the sender is closed because the manager has taken over the role of the sender.

If you wish to clear your clipboard, then use SDL_SetClipboardText(NULL); or SDL_CearClipboardData(); after processing the clipboard contents. I think the default behavior for most programs is to assume that the clipboard data might be needed by the user again and by other programs, so by default it’s not common practice to clear the clipboard after consuming the content… But if you offered a button that lets the user chose to clear the clipboard then this would be useful.

There is also this event you could listen for changes in the clipboard:
SDL_EVENT_CLIPBOARD_UPDATE = 0x900, /**< The clipboard or primary selection changed */

I think the code to the SDL_SetClipboardTextCallback function is outdated and there is no mime-handling inside this default callback. Whatever the reason, it doesn’t seem to process mime requests, and the default behavior is to send the data no matter what. If any type of mime request for data occurs, the text will be served. This is good reason to avoid SDL_SetClipboardText().

I don’t see any way to change the callback used by SDL_SetClipboardText, but you can still send text through SDL_SetClipboardData if you want to change this default behavior.

On the other hand: When you set up your own mime-handling callback for SDL_SetClipboardData, the docs say here that sending no data is somehow considered undefined behavior and might cause issue for some programs that aren’t set up to handle such an instance, which is also confusing. Perhaps this is more of a problem in some systems and not others?

All I can say is that if I have a portion of an image from Krita stored in clipboard and I request a text mime-type, I get NULL back as a pointer and the size is reported as zero. If I try to pass text to Krita while a portion of an image is selected, Krita is able to say that the data it was handed was not a png/bmp and ignores the paste request.

So just always be safe, if you request data from the clipboard in non-text format, then confirm that it’s not NULL, then confirm that it is indeed in that format before loading it into a surface/texture:

case SDLK_V:
{
    // request paste;
    size_t len = 0;
    void * data = SDL_GetClipboardData("image/png", &len);
    if(data)
    {
            unsigned char pngMagic[8] = {0X89, 0X50, 0X4E, 0X47, 0X0D, 0X0A, 0X1A, 0X0A};
            if(strncmp((char*)data, (char*)pngMagic, 8) == 0)
            {
                SDL_Log("PNG Confirmed");
            }
            else
            {
                 SDL_Log("The data is not PNG Formatted.");
            }
     }
     else
     {
            SDL_Log("Data was NULL, length was %d", len);
     }
}
break;

I could only replicate this kind of behavior when I wasn’t polling a window for events or rendering the window which lead to strange behavior, but the clipboard data and callbacks look to be tied to the window’s video device structure, so that kind of made sense at the time… Do you have example code to replicate this?

Here’s a small program that is able to request and display images from the clipboard.
It checks the clipboard when you hit the “v” key.
Since the functions for IMG_LoadTexture() are so robust, we can use it to test whether the clipboard contains a path to an image or not, if that fails then we use the _IO version for if the clipboard was serving an image’s data instead.
(“copy image” from an internet browser will copy the image data into the clipboard, while hitting ctrl-c in a file explorer is likely to only serve the path to the image as text)

#include <SDL3/SDL.h>
#include <SDL3/SDL_image.h>

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG);
	SDL_Window * win = SDL_CreateWindow("title", 400, 400, SDL_WINDOW_RESIZABLE);
	SDL_Renderer * screen = SDL_CreateRenderer(win, 0);
	SDL_SetRenderVSync(screen, 1);

	SDL_Texture * image = NULL;

	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_KEY_DOWN:
					switch(ev.key.key)
					{
						case SDLK_V:
						{
							if(image)
							{
								// don't want a memory leak.
								SDL_DestroyTexture(image);
								image = NULL;
							}

							// let's just try text, maybe it's the path to the file.
							void * data = SDL_GetClipboardText();
							image = IMG_LoadTexture(screen, (char*) data);
							if(data)
							{
								SDL_free(data);
								data = NULL;
							}
							if(!image)
							{
								// trying a text path failed, so let's try looking at png data
								size_t len = 0;
								data = SDL_GetClipboardData("image/png", &len);
								if(!data)
								{
									// might as well ask for a jpeg if all else failed.
									data = SDL_GetClipboardData("image/jpeg", &len);
								}
								image = IMG_LoadTexture_IO(screen, SDL_IOFromMem(data, len), 1);
							}
							if(data)
							{
								SDL_free(data);
							}
						}
						break;
					}
					break;
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}

		if(image)
		{
			SDL_RenderTexture(screen, image, NULL, NULL);
		}
		SDL_RenderPresent(screen);
	}

	SDL_DestroyWindow(win);
	IMG_Quit();
	SDL_Quit();
}

@GuildedDoughnut I remember having problems with the clipboard data disappearing after closing programs when I first started to use Linux about 15 years ago but after that I haven’t had any problems with this (at least with text) so I don’t think this is expected behaviour. I have never had to install a “clipboard manager”.

I tested copying “image data” with a few different applications. With GIMP, LibreOffice Draw and Krita the clipboard data was still intact after closing the application. That was not the case with Inkscape (Text copied in Inkscape could still be pasted though).

I agree, but I wasn’t “consuming” the clipboard content here. I set a new clipboard text and I didn’t expect that to also provide corrupted clipboard data for all mime types.

I did use that one. It worked for me.

I guess this could be useful sometimes (e.g. to enable the “paste” button UI) but it’s not normally where you would want to “consume” the clipboard data. There doesn’t seem to be a direct way to know what clipboard data that has been made available. I guess you will just have to check the text and/or all mime types that you support to know if it’s something you can use. Note that this event is also fired when the “primary selection text” is updated which seems to happen when you just select some text in another application.

What do you suggest using instead? SDL_SetClipboardData with “text/plain” mime type? That’s a lot more complicated, but I tried, and it didn’t work. SDL still reported it as text clipboard and data clipboard for all mime types and I wasn’t even able to paste the text in other programs.

When you call SDL_SetClipboardData you also pass an array specifying which mime types you support so your callback should at least handle all of those.

You should be able to use SDL_HasClipboardData to do this. It doesn’t work right now, but I expect it will get fixed eventually… (hopefully before SDL 3.2)

Or just let the loading fail… It should be safe to call IMG_Load_IO with non-image data format. There is also IMG_isPNG, IMG_isJPG, etc. that you can use if you want to check the file signatures (IMG_Load_IO uses these internally to detect the format).

The following program will always display the current clipboard text in the window title. When you press V it will print the current clipboard text and whether the clipboard data is set. Then it will get the clipboard data and print the same info again.

#include <SDL3/SDL.h>
#include <stdbool.h>

void log_clipboard()
{
	SDL_Log("\tClipboard text: %s", SDL_HasClipboardText() ? SDL_GetClipboardText() : "no clipboard text");
	SDL_Log("\tHas clipboard data? %s", SDL_HasClipboardData("image/png") ? "true" : "false");
}

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window * win = SDL_CreateWindow("Example", 300, 200, 0);
	SDL_Renderer * screen = SDL_CreateRenderer(win, 0);
	SDL_SetRenderVSync(screen, 1);

	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_KEY_DOWN:
					switch(ev.key.key)
					{
						case SDLK_V:
						{
							SDL_Log("Before calling SDL_GetClipboardData:");
							log_clipboard();
							
							size_t len;
							SDL_GetClipboardData("image/png", &len);
							
							SDL_Log("After calling SDL_GetClipboardData:");
							log_clipboard();
						}
						break;
					}
					break;
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}

		SDL_SetWindowTitle(win, SDL_HasClipboardText() ? SDL_GetClipboardText() : "no clipboard text");
	}
	
	SDL_Quit();
}
  1. Compile the program
  2. Copy “some text”
  3. Start the program
  4. Note that “some text” is displayed in the window title
  5. Go into GIMP or Inkscape and copy some image data
  6. Note that the program still displays “some text” in the title
  7. Press V when the program has focus and look at the output:
INFO: Before calling SDL_GetClipboardData:
INFO: 	Clipboard text: some text
INFO: 	Has clipboard data? true
INFO: After calling SDL_GetClipboardData:
INFO: 	Clipboard text: no clipboard text
INFO: 	Has clipboard data? true
  1. Note how the clipboard text got magically cleared when SDL_GetClipboardData was called which is now also reflected in the window title.

To me this behaviour does not make much sense. The clipboard text should have been cleared already when the clipboard data was set. It seems like this problem is internal to the program/SDL because if I do step 5 before step 3 the clipboard text will not be set.

2 Likes

Can you report this as a bug on GitHub, for tracking?
Issues · libsdl-org/SDL (github.com)

Thanks!

If you log a report on the github, it seems to be an X11 specific issue.
The code works mostly as expected on Wayland except that the window title doesn’t update correctly until the window has gained focus.

Done! See #10192.

1 Like

Oh, you’re right, that is no longer a problem in X11 Gnome (both Wayland and X11 versions), and since Fedora and Ubuntu are using Gnome in vanilla installs for the last couple years, it’s probably not a problem for a large fraction of the Linux community. It’s still an issue for certain window managers like iceWM, but it’s more of an edge case now. Cool.