glfw / glfw

A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input
https://www.glfw.org
zlib License
13.12k stars 5.27k forks source link

Generic Clipboard API #2633

Open madd-games opened 1 month ago

madd-games commented 1 month ago

Current GLFW has the function glfwSetClipboardText() and glfwGetClipboardText(), but the underlying system clipboard APIs are significantly more powerful:

We could add support for this by expanding the API. I am happy to work on this, but would like some feedback first. Here is my proposed API.

Clipboard Offer Objects

We could have an opaque structure GLFWcboffer, which is used to define offers to the clipboard. We would create it with a user-defined data pointer, and a "cancel" callback.

typedef void (* GLFWcboffercancelfun)(void* userdata);
GLFWAPI GLFWcboffer* glfwCreateClipboardOffer(void* userdata, GLFWcboffercancelfun cancelCallback);

The cancel callback is invoked when the app loses ownership of the clipboard (another app inserted something into it).

Offering different types

On some platforms like Windows, there are some standard predefined types such as "text" and "bitmap", whereas on other platforms there are no predefined types and we provide the MIME type of the data instead. To make the API generic, we need special handling for those predefined types, as well the ability to add custom types. In each case, we pass a "send" callback function, which will be called to provide the data when it is needed. For strings we can have this:

typedef char* (* GLFWstringsendfun)(void* userdata);
GLFWAPI void glfwOfferString(GLFWcboffer* offer, GLFWstringsendfun stringSendCallback);

GLFWstringsendfun could return a heap-allocated UTF-8 string, which GLFW will then free(). On Windows this would encode the string into UTF-16 (as glfwSetClipboardString() already does) and add it to the clipboard as CF_UNICODETEXT, whereas on Wayland it would offer it as text/plain;charset=utf-8 (again, as the existing API already does). The main difference would be that it is deferred.

We could have a similar API for offering images, but I haven't looked into it yet (there is no API for storing images in the clipboard yet, but some people have proposed them).

We could have a generic "binary blob" API as well, which could look like this:

typedef void (* GLFWblobsendfun)(void* userdata, void** outptr, size_t* outsize);
GLFWAPI void glfwOfferBlob(GLFWcboffer* offer, const char* mimetype, GLFWblobsendfun blobSendCallback);

The callback would set *outptr to point to a heap-allocated buffer with arbitrary binary data, and *outsize to its size in bytes. GLFW would free the buffer. mimetype would be used to identify the type. On Wayland it would simply pass the MIME type through, on Windows it would call RegisterClipboardFormat() to assign an ID for the mime type and then pass that through.

Acquiring the clipboard

After adding callbacks for one or more types to the offer, a function like this would be used:

GLFWAPI void glfwAcquireClipboard(GLFWwindow* window, GLFWcboffer* offer);

This function would take ownership of the offer. When another application tries to paste a specific type, the appropriate "send" callback is invoked. If another application changes the clipboard content, the "cancel" callback will be invoked.

Getting the clipboard data

We could have separate functions for getting clipboard data in various formats. The existing glfwGetClipboardString() could be used to read strings as before. We could add a function for reading binary blobs too:

GLFWAPI void glfwGetClipboardBlob(GLFWwindow* window, const char* mimetype, const void** outptr, size_t* outsize);

The returned pointer would be valid until the next call to any of the GetClipboard functions.

Fallback

There is a good fallback mechanism for any backend that cannot support all of the features. For example, if a backend only supports copying strings, then glfwAcquireClipboard() could simply immediately invoke the string send callback, and send over the string.

Example: glfwSetClipboardString

With this API, glfwSetClipboardString() could be reimplemented like this (ignoring error handling):

static char* sendString(void* userdata)
{
    return strdup(userdata);
}

GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string)
{
    GLFWcboffer* offer = glfwCreateClipboardOffer(strdup(string), _glfw_free);
    glfwOfferString(offer, sendString);
    glfwAcquireClipboard(window, offer);
}

Use cases

This is a more efficient way of copying large amount of data, is supported by all major operating systems, and allows other types of data to be sent (such as binary blobs) and not just text. One example of where this would be useful is this PR I've raised against Goxel:

https://github.com/guillaumechereau/goxel/pull/395

Furthermore, SDL3 has added support for this as well:

https://wiki.libsdl.org/SDL3/SDL_SetClipboardData

Let me know of any comments or suggestions, and I'll be happy to begin implementing this.

HazardousPeach commented 1 month ago

2636 is related; it's a much lower-level solution that puts most of the work on the client, but allows client to implement this functionality for X11 within GLFW.