UI: Allow copy & paste images in linux/Wayland #119117

Manually merged
Campbell Barton merged 3 commits from Jose-Vicente-Barrachina/blender:wl-image-clipboard into main 2024-03-12 07:43:31 +01:00
3 changed files with 173 additions and 4 deletions

View File

@ -108,6 +108,9 @@ static bool has_libdecor = true;
# endif
#endif
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
/* -------------------------------------------------------------------- */
/** \name Forward Declarations
* \{ */
@ -7473,6 +7476,153 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
}
}
static constexpr const char *ghost_wl_mime_img_png = "image/png";
GHOST_TSuccess GHOST_SystemWayland::hasClipboardImage(void) const
{
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return GHOST_kFailure;
}
GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
if (data_offer) {
if (data_offer->types.count(ghost_wl_mime_img_png)) {
return GHOST_kSuccess;
}
}
return GHOST_kFailure;
}
uint *GHOST_SystemWayland::getClipboardImage(int *r_width, int *r_height) const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return nullptr;
};
std::mutex &mutex = seat->data_offer_copy_paste_mutex;
mutex.lock();
bool mutex_locked = true;
uint *rgba = nullptr;
GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
if (data_offer) {
/* Check if the source offers a supported mime type.
* This check could be skipped, because the paste option is not supposed to be enabled
* otherwise. */
if (data_offer->types.count(ghost_wl_mime_img_png)) {
/* Receive the clipboard in a thread, performing round-trips while waiting,
* so pasting content from own 'primary->data_source' doesn't hang. */
struct ThreadResult {
char *data = nullptr;
std::atomic<bool> done = false;
} thread_result;
size_t data_len = 0;
auto read_clipboard_fn = [&data_len](GWL_DataOffer *data_offer,
const char *mime_receive,
std::mutex *mutex,
ThreadResult *thread_result) {
thread_result->data = read_buffer_from_data_offer(data_offer,
mime_receive,
mutex,
true,
&data_len);
thread_result->done = true;
};
std::thread read_thread(read_clipboard_fn,
data_offer,
ghost_wl_mime_img_png,
&mutex,
&thread_result);
read_thread.detach();
while (!thread_result.done) {
wl_display_roundtrip(display_->wl.display);
}
/* Generate the image buffer with the recieved data */
ImBuf *ibuf = IMB_ibImageFromMemory((uint8_t *)thread_result.data,
data_len,
IB_rect,
nullptr,
"<clipboard>");
if (ibuf) {
*r_width = ibuf->x;
*r_height = ibuf->y;
const uint64_t byte_count = uint64_t(ibuf->x) * ibuf->y * 4;
rgba = (uint *)malloc(byte_count);
std::memcpy(rgba, ibuf->byte_buffer.data, byte_count);
IMB_freeImBuf(ibuf);
}
/* After reading the data offer, the mutex gets unlocked */
mutex_locked = false;
}
}
if (mutex_locked) {
mutex.unlock();
}
return rgba;
}
GHOST_TSuccess GHOST_SystemWayland::putClipboardImage(uint *rgba, int width, int height) const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
/* Create a wl_data_source object */
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return GHOST_kFailure;
}
std::lock_guard lock(seat->data_source_mutex);
GWL_DataSource *data_source = seat->data_source;
/* Load buffer into an ImBuf and convert to PNG */
ImBuf *ibuf = IMB_allocFromBuffer(reinterpret_cast<uint8_t *>(rgba), nullptr, width, height, 32);
ibuf->ftype = IMB_FTYPE_PNG;
ibuf->foptions.quality = 15;
if (!IMB_saveiff(ibuf, "<memory>", IB_rect | IB_mem)) {
IMB_freeImBuf(ibuf);
return GHOST_kFailure;
}
/* Copy ImBuf encoded_buffer to data source */
GWL_SimpleBuffer *imgbuffer = &data_source->buffer_out;
gwl_simple_buffer_free_data(imgbuffer);
imgbuffer->data_size = ibuf->encoded_buffer_size;
char *data = static_cast<char *>(malloc(imgbuffer->data_size));
std::memcpy(data, ibuf->encoded_buffer.data, ibuf->encoded_buffer_size);
imgbuffer->data = data;
data_source->wl.source =
wl_data_device_manager_create_data_source(display_->wl.data_device_manager);
wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
/* Advertise the mime types supported */
wl_data_source_offer(data_source->wl.source, "image/png");
if (seat->wl.data_device) {
wl_data_device_set_selection(seat->wl.data_device,
data_source->wl.source,
seat->data_source_serial);
}
IMB_freeImBuf(ibuf);
return GHOST_kSuccess;
}
uint8_t GHOST_SystemWayland::getNumDisplays() const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
@ -8048,9 +8198,7 @@ GHOST_TCapabilityFlag GHOST_SystemWayland::getCapabilities() const
* is negligible. */
GHOST_kCapabilityGPUReadFrontBuffer |
/* This WAYLAND back-end has not yet implemented desktop color sample. */
GHOST_kCapabilityDesktopSample |
/* This WAYLAND back-end has not yet implemented image copy/paste. */
GHOST_kCapabilityClipboardImages));
GHOST_kCapabilityDesktopSample));
}
bool GHOST_SystemWayland::cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode)

View File

@ -159,6 +159,27 @@ class GHOST_SystemWayland : public GHOST_System {
void putClipboard(const char *buffer, bool selection) const override;
/**
* Returns GHOST_kSuccess if the clipboard contains an image.
*/
GHOST_TSuccess hasClipboardImage(void) const override;
/**
* Get image data from the Clipboard
* \param r_width: the returned image width in pixels.
* \param r_height: the returned image height in pixels.
* \return pointer uint array in RGBA byte order. Caller must free.
*/
uint *getClipboardImage(int *r_width, int *r_height) const override;
/**
* Put image data to the Clipboard
* \param rgba: uint array in RGBA byte order.
* \param width: the image width in pixels.
* \param height: the image height in pixels.
*/
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override;
uint8_t getNumDisplays() const override;
uint64_t getMilliSeconds() const override;

View File

@ -211,7 +211,7 @@ class IMAGE_MT_image(Menu):
layout.separator()
if sys.platform[:3] == "win":
if sys.platform[:3] in ["win", "lin"]:
layout.operator("image.clipboard_copy", text="Copy")
layout.operator("image.clipboard_paste", text="Paste")
layout.separator()