Fix T98758: Crash setting the clipboard in Wayland
Accessing the clipboard in wayland wasn't thread safe, use locks for the clipboard, drag & drop data access and when setting the systems clipboard.
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
static GHOST_IWindow *get_window(struct wl_surface *surface);
|
||||
|
||||
@@ -91,8 +92,6 @@ struct data_offer_t {
|
||||
|
||||
struct data_source_t {
|
||||
struct wl_data_source *data_source;
|
||||
/** Last device that was active. */
|
||||
uint32_t source_serial;
|
||||
char *buffer_out;
|
||||
};
|
||||
|
||||
@@ -138,10 +137,17 @@ struct input_t {
|
||||
struct wl_data_device *data_device = nullptr;
|
||||
/** Drag & Drop. */
|
||||
struct data_offer_t *data_offer_dnd;
|
||||
std::mutex data_offer_dnd_lock;
|
||||
|
||||
/** Copy & Paste. */
|
||||
struct data_offer_t *data_offer_copy_paste;
|
||||
std::mutex data_offer_copy_paste_mutex;
|
||||
|
||||
struct data_source_t *data_source;
|
||||
std::mutex data_source_mutex;
|
||||
|
||||
/** Last device that was active. */
|
||||
uint32_t data_source_serial;
|
||||
};
|
||||
|
||||
struct display_t {
|
||||
@@ -174,6 +180,9 @@ struct display_t {
|
||||
|
||||
static GHOST_WindowManager *window_manager = nullptr;
|
||||
|
||||
/** Check this lock before accessing `GHOST_SystemWayland::selection` from a thread. */
|
||||
static std::mutex system_selection_mutex;
|
||||
|
||||
/**
|
||||
* Callback for WAYLAND to run when there is an error.
|
||||
*
|
||||
@@ -509,6 +518,7 @@ static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
|
||||
|
||||
static void dnd_events(const input_t *const input, const GHOST_TEventType event)
|
||||
{
|
||||
/* NOTE: `input->data_offer_dnd_lock` must already be locked. */
|
||||
const uint64_t time = input->system->getMilliSeconds();
|
||||
GHOST_IWindow *const window = static_cast<GHOST_WindowWayland *>(
|
||||
wl_surface_get_user_data(input->focus_dnd));
|
||||
@@ -523,7 +533,9 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event)
|
||||
}
|
||||
}
|
||||
|
||||
static std::string read_pipe(data_offer_t *data_offer, const std::string mime_receive)
|
||||
static std::string read_pipe(data_offer_t *data_offer,
|
||||
const std::string mime_receive,
|
||||
std::mutex *mutex)
|
||||
{
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) != 0) {
|
||||
@@ -532,6 +544,13 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re
|
||||
wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
|
||||
data_offer->in_use.store(false);
|
||||
|
||||
if (mutex) {
|
||||
mutex->unlock();
|
||||
}
|
||||
/* WARNING: `data_offer` may be freed from now on. */
|
||||
|
||||
std::string data;
|
||||
ssize_t len;
|
||||
char buffer[4096];
|
||||
@@ -539,7 +558,6 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re
|
||||
data.insert(data.end(), buffer, buffer + len);
|
||||
}
|
||||
close(pipefd[0]);
|
||||
data_offer->in_use.store(false);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -562,7 +580,10 @@ static void data_source_send(void *data,
|
||||
const char * /*mime_type*/,
|
||||
int32_t fd)
|
||||
{
|
||||
const char *const buffer = static_cast<char *>(data);
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
std::lock_guard lock{input->data_source_mutex};
|
||||
|
||||
const char *const buffer = input->data_source->buffer_out;
|
||||
if (write(fd, buffer, strlen(buffer)) < 0) {
|
||||
GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl);
|
||||
}
|
||||
@@ -679,6 +700,8 @@ static void data_device_enter(void *data,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
std::lock_guard lock{input->data_offer_dnd_lock};
|
||||
|
||||
input->data_offer_dnd = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
|
||||
data_offer_t *data_offer = input->data_offer_dnd;
|
||||
|
||||
@@ -702,6 +725,7 @@ static void data_device_enter(void *data,
|
||||
static void data_device_leave(void *data, struct wl_data_device * /*wl_data_device*/)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
std::lock_guard lock{input->data_offer_dnd_lock};
|
||||
|
||||
dnd_events(input, GHOST_kEventDraggingExited);
|
||||
input->focus_dnd = nullptr;
|
||||
@@ -720,6 +744,8 @@ static void data_device_motion(void *data,
|
||||
wl_fixed_t y)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
std::lock_guard lock{input->data_offer_dnd_lock};
|
||||
|
||||
input->data_offer_dnd->dnd.x = wl_fixed_to_int(x);
|
||||
input->data_offer_dnd->dnd.y = wl_fixed_to_int(y);
|
||||
dnd_events(input, GHOST_kEventDraggingUpdated);
|
||||
@@ -728,6 +754,8 @@ static void data_device_motion(void *data,
|
||||
static void data_device_drop(void *data, struct wl_data_device * /*wl_data_device*/)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
std::lock_guard lock{input->data_offer_dnd_lock};
|
||||
|
||||
data_offer_t *data_offer = input->data_offer_dnd;
|
||||
|
||||
const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(),
|
||||
@@ -742,7 +770,7 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic
|
||||
const int x = data_offer->dnd.x;
|
||||
const int y = data_offer->dnd.y;
|
||||
|
||||
const std::string data = read_pipe(data_offer, mime_receive);
|
||||
const std::string data = read_pipe(data_offer, mime_receive, nullptr);
|
||||
|
||||
wl_data_offer_finish(data_offer->id);
|
||||
wl_data_offer_destroy(data_offer->id);
|
||||
@@ -808,6 +836,9 @@ static void data_device_selection(void *data,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
std::lock_guard lock{input->data_offer_copy_paste_mutex};
|
||||
|
||||
data_offer_t *data_offer = input->data_offer_copy_paste;
|
||||
|
||||
/* Delete old data offer. */
|
||||
@@ -825,22 +856,28 @@ static void data_device_selection(void *data,
|
||||
data_offer = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
|
||||
input->data_offer_copy_paste = data_offer;
|
||||
|
||||
std::string mime_receive;
|
||||
for (const std::string type : {mime_text_utf8, mime_text_plain}) {
|
||||
if (data_offer->types.count(type)) {
|
||||
mime_receive = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto read_selection = [](input_t *input) {
|
||||
GHOST_SystemWayland *const system = input->system;
|
||||
input->data_offer_copy_paste_mutex.lock();
|
||||
|
||||
auto read_selection = [](GHOST_SystemWayland *const system,
|
||||
data_offer_t *data_offer,
|
||||
const std::string mime_receive) {
|
||||
const std::string data = read_pipe(data_offer, mime_receive);
|
||||
system->setSelection(data);
|
||||
data_offer_t *data_offer = input->data_offer_copy_paste;
|
||||
std::string mime_receive;
|
||||
for (const std::string type : {mime_text_utf8, mime_text_plain}) {
|
||||
if (data_offer->types.count(type)) {
|
||||
mime_receive = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const std::string data = read_pipe(
|
||||
data_offer, mime_receive, &input->data_offer_copy_paste_mutex);
|
||||
|
||||
{
|
||||
std::lock_guard lock{system_selection_mutex};
|
||||
system->setSelection(data);
|
||||
}
|
||||
};
|
||||
|
||||
std::thread read_thread(read_selection, input->system, data_offer, mime_receive);
|
||||
std::thread read_thread(read_selection, input);
|
||||
read_thread.detach();
|
||||
}
|
||||
|
||||
@@ -1066,7 +1103,7 @@ static void pointer_button(void *data,
|
||||
break;
|
||||
}
|
||||
|
||||
input->data_source->source_serial = serial;
|
||||
input->data_source_serial = serial;
|
||||
input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
|
||||
input->system->pushEvent(new GHOST_EventButton(
|
||||
input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
|
||||
@@ -1241,7 +1278,7 @@ static void keyboard_key(void *data,
|
||||
key_data.utf8_buf[0] = '\0';
|
||||
}
|
||||
|
||||
input->data_source->source_serial = serial;
|
||||
input->data_source_serial = serial;
|
||||
|
||||
GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>(
|
||||
wl_surface_get_user_data(input->focus_keyboard));
|
||||
@@ -1642,7 +1679,11 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c
|
||||
return;
|
||||
}
|
||||
|
||||
data_source_t *data_source = d->inputs[0]->data_source;
|
||||
input_t *input = d->inputs[0];
|
||||
|
||||
std::lock_guard lock{input->data_source_mutex};
|
||||
|
||||
data_source_t *data_source = input->data_source;
|
||||
|
||||
/* Copy buffer. */
|
||||
free(data_source->buffer_out);
|
||||
@@ -1652,16 +1693,15 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c
|
||||
|
||||
data_source->data_source = wl_data_device_manager_create_data_source(d->data_device_manager);
|
||||
|
||||
wl_data_source_add_listener(
|
||||
data_source->data_source, &data_source_listener, data_source->buffer_out);
|
||||
wl_data_source_add_listener(data_source->data_source, &data_source_listener, input);
|
||||
|
||||
for (const std::string &type : mime_send) {
|
||||
wl_data_source_offer(data_source->data_source, type.c_str());
|
||||
}
|
||||
|
||||
if (!d->inputs.empty() && d->inputs[0]->data_device) {
|
||||
if (input->data_device) {
|
||||
wl_data_device_set_selection(
|
||||
d->inputs[0]->data_device, data_source->data_source, data_source->source_serial);
|
||||
input->data_device, data_source->data_source, input->data_source_serial);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user