video/out/wayland_common: fix crash with multi-seat drag and drop

If you have two seats connected, both with the pointer capability, and
use two hands to start a drag and drop operation with each pointer, then
move one pointer to the mpv window, then the other, mpv fails the
!wl->dnd_offer->offer assert in device.enter, because it expects that
the compositor wouldn't just call device.enter again before device.leave
or offer.finish.

The data device is per-seat, so this commit makes mpv track the data
device's offers per-seat rather than in vo_wayland_state, which makes
more sense and is easier to reason about when multi-seat is involved.
This commit is contained in:
Attila Fidan
2025-01-26 05:41:38 +00:00
committed by Dudemanguy
parent 3550ec52cd
commit 98627b027d
2 changed files with 33 additions and 32 deletions

View File

@@ -192,6 +192,9 @@ struct vo_wayland_seat {
struct wl_pointer *pointer;
struct wl_touch *touch;
struct wl_data_device *dnd_ddev;
struct vo_wayland_data_offer *pending_offer;
struct vo_wayland_data_offer *dnd_offer;
struct vo_wayland_data_offer *selection_offer;
struct vo_wayland_text_input *text_input;
/* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */
void *cursor_shape_device;
@@ -707,7 +710,7 @@ static void data_offer_handle_offer(void *data, struct wl_data_offer *offer,
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->pending_offer;
struct vo_wayland_data_offer *o = s->pending_offer;
int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type);
if (o->offer && score > o->mime_score && wl->opts->drag_and_drop != -2) {
o->mime_score = score;
@@ -726,7 +729,7 @@ static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, u
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->dnd_offer;
struct vo_wayland_data_offer *o = s->dnd_offer;
if (dnd_action && wl->opts->drag_and_drop != -2) {
if (wl->opts->drag_and_drop >= 0) {
o->action = wl->opts->drag_and_drop;
@@ -755,8 +758,7 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_
struct wl_data_offer *id)
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->pending_offer;
struct vo_wayland_data_offer *o = s->pending_offer;
destroy_offer(o);
o->offer = id;
@@ -770,16 +772,16 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev,
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->pending_offer;
struct vo_wayland_data_offer *o = s->pending_offer;
if (o->offer != id) {
MP_FATAL(wl, "DND offer ID mismatch!\n");
return;
}
assert(!wl->dnd_offer->offer);
*wl->dnd_offer = *wl->pending_offer;
*wl->pending_offer = (struct vo_wayland_data_offer){.fd = -1};
o = wl->dnd_offer;
assert(!s->dnd_offer->offer);
*s->dnd_offer = *s->pending_offer;
*s->pending_offer = (struct vo_wayland_data_offer){.fd = -1};
o = s->dnd_offer;
if (wl->opts->drag_and_drop != -2) {
wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
@@ -794,7 +796,7 @@ static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev)
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->dnd_offer;
struct vo_wayland_data_offer *o = s->dnd_offer;
if (o->offer) {
if (o->fd != -1)
@@ -815,8 +817,7 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_ddev
uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->dnd_offer;
struct vo_wayland_data_offer *o = s->dnd_offer;
wl_data_offer_accept(o->offer, time, o->mime_type);
}
@@ -824,7 +825,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev)
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->dnd_offer;
struct vo_wayland_data_offer *o = s->dnd_offer;
int pipefd[2];
@@ -847,18 +848,18 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
{
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
struct vo_wayland_data_offer *o = wl->pending_offer;
struct vo_wayland_data_offer *o = s->pending_offer;
if (o->offer != id) {
MP_FATAL(wl, "Selection offer ID mismatch!\n");
return;
}
if (wl->selection_offer->offer) {
destroy_offer(wl->selection_offer);
if (s->selection_offer->offer) {
destroy_offer(s->selection_offer);
MP_VERBOSE(wl, "Received a new selection offer. Releasing the previous offer.\n");
}
*wl->selection_offer = *wl->pending_offer;
*wl->pending_offer = (struct vo_wayland_data_offer){.fd = -1};
*s->selection_offer = *s->pending_offer;
*s->pending_offer = (struct vo_wayland_data_offer){.fd = -1};
if (!id)
return;
@@ -869,7 +870,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
return;
}
o = wl->selection_offer;
o = s->selection_offer;
// Only receive plain text for now, may expand later.
if (o->offered_plain_text)
wl_data_offer_receive(o->offer, "text/plain;charset=utf-8", pipefd[1]);
@@ -1843,6 +1844,10 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id
struct vo_wayland_seat *seat = talloc_zero(wl, struct vo_wayland_seat);
seat->wl = wl;
seat->id = id;
seat->pending_offer = talloc_zero(seat, struct vo_wayland_data_offer);
seat->dnd_offer = talloc_zero(seat, struct vo_wayland_data_offer);
seat->selection_offer = talloc_zero(seat, struct vo_wayland_data_offer);
seat->pending_offer->fd = seat->dnd_offer->fd = seat->selection_offer->fd = -1;
seat->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver);
wl_seat_add_listener(seat->seat, &seat_listener, seat);
wl_list_insert(&wl->seat_list, &seat->link);
@@ -2548,6 +2553,10 @@ static void remove_seat(struct vo_wayland_seat *seat)
if (seat->xkb_state)
xkb_state_unref(seat->xkb_state);
destroy_offer(seat->pending_offer);
destroy_offer(seat->dnd_offer);
destroy_offer(seat->selection_offer);
wl_seat_destroy(seat->seat);
talloc_free(seat);
return;
@@ -3015,8 +3024,11 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
switch (request) {
case VOCTRL_CHECK_EVENTS: {
check_fd(wl, wl->dnd_offer, true);
check_fd(wl, wl->selection_offer, false);
struct vo_wayland_seat *seat;
wl_list_for_each(seat, &wl->seat_list, link) {
check_fd(wl, seat->dnd_offer, true);
check_fd(wl, seat->selection_offer, false);
}
*events |= wl->pending_vo_events;
if (*events & VO_EVENT_RESIZE) {
*events |= VO_EVENT_EXPOSE;
@@ -3235,13 +3247,9 @@ bool vo_wayland_init(struct vo *vo)
.scaling = WAYLAND_SCALE_FACTOR,
.wakeup_pipe = {-1, -1},
.display_fd = -1,
.pending_offer = talloc_zero(wl, struct vo_wayland_data_offer),
.dnd_offer = talloc_zero(wl, struct vo_wayland_data_offer),
.selection_offer = talloc_zero(wl, struct vo_wayland_data_offer),
.cursor_visible = true,
.opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts),
};
wl->pending_offer->fd = wl->dnd_offer->fd = wl->selection_offer->fd = -1;
wl->opts = wl->opts_cache->opts;
wl_list_init(&wl->output_list);
@@ -3479,10 +3487,6 @@ void vo_wayland_uninit(struct vo *vo)
if (!wl)
return;
destroy_offer(wl->pending_offer);
destroy_offer(wl->dnd_offer);
destroy_offer(wl->selection_offer);
mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL);
if (wl->compositor)

View File

@@ -166,9 +166,6 @@ struct vo_wayland_state {
/* Data offer */
struct wl_data_device_manager *devman;
struct vo_wayland_data_offer *pending_offer;
struct vo_wayland_data_offer *dnd_offer;
struct vo_wayland_data_offer *selection_offer;
bstr selection_text;
/* Cursor */