From 0db5462c1b190a5a3385caf7c39a3292abb2ecb4 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:51:53 -0500 Subject: [PATCH 1/3] video/out/wayland_common: extract data offer to a struct The data offers can be either dnd or selection offers. Add a separate struct for it so that handling of selection offers can be added. --- video/out/wayland_common.c | 123 ++++++++++++++++++++----------------- video/out/wayland_common.h | 9 +-- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 4d67087de9824..14352dd5b01cd 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -221,6 +221,14 @@ struct vo_wayland_tranche { struct wl_list link; }; +struct vo_wayland_data_offer { + struct wl_data_offer *offer; + int action; // actually enum mp_dnd_action + char *mime_type; + int fd; + int mime_score; +}; + static bool single_output_spanned(struct vo_wayland_state *wl); static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, @@ -688,11 +696,12 @@ 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->dnd_offer; int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type); - if (score > wl->dnd_mime_score && wl->opts->drag_and_drop != -2) { - wl->dnd_mime_score = score; - talloc_replace(wl, wl->dnd_mime_type, mime_type); - MP_VERBOSE(wl, "Given DND offer with mime type %s\n", wl->dnd_mime_type); + if (score > o->mime_score && wl->opts->drag_and_drop != -2) { + o->mime_score = score; + talloc_replace(wl, o->mime_type, mime_type); + MP_VERBOSE(wl, "Given DND offer with mime type %s\n", o->mime_type); } } @@ -704,12 +713,13 @@ 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; if (dnd_action && wl->opts->drag_and_drop != -2) { if (wl->opts->drag_and_drop >= 0) { - wl->dnd_action = wl->opts->drag_and_drop; + o->action = wl->opts->drag_and_drop; } else { - wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ? - DND_REPLACE : DND_APPEND; + o->action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ? + DND_REPLACE : DND_APPEND; } static const char * const dnd_action_names[] = { @@ -718,7 +728,7 @@ static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, u [DND_INSERT_NEXT] = "DND_INSERT_NEXT", }; - MP_VERBOSE(wl, "DND action is %s\n", dnd_action_names[wl->dnd_action]); + MP_VERBOSE(wl, "DND action is %s\n", dnd_action_names[o->action]); } } @@ -733,10 +743,11 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; - if (wl->dnd_offer) - wl_data_offer_destroy(wl->dnd_offer); + struct vo_wayland_data_offer *o = wl->dnd_offer; + if (o->offer) + wl_data_offer_destroy(o->offer); - wl->dnd_offer = id; + o->offer = id; wl_data_offer_add_listener(id, &data_offer_listener, s); } @@ -747,7 +758,8 @@ 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; - if (wl->dnd_offer != id) { + struct vo_wayland_data_offer *o = wl->dnd_offer; + if (o->offer != id) { MP_FATAL(wl, "DND offer ID mismatch!\n"); return; } @@ -756,8 +768,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); - wl_data_offer_accept(id, serial, wl->dnd_mime_type); - MP_VERBOSE(wl, "Accepting DND offer with mime type %s\n", wl->dnd_mime_type); + wl_data_offer_accept(id, serial, o->mime_type); + MP_VERBOSE(wl, "Accepting DND offer with mime type %s\n", o->mime_type); } } @@ -766,19 +778,20 @@ 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; - if (wl->dnd_offer) { - if (wl->dnd_fd != -1) + if (o->offer) { + if (o->fd != -1) return; - wl_data_offer_destroy(wl->dnd_offer); - wl->dnd_offer = NULL; + wl_data_offer_destroy(o->offer); + o->offer = NULL; } if (wl->opts->drag_and_drop != -2) { - MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", wl->dnd_mime_type); - if (wl->dnd_mime_type) - TA_FREEP(&wl->dnd_mime_type); - wl->dnd_mime_score = 0; + MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", o->mime_type); + if (o->mime_type) + TA_FREEP(&o->mime_type); + o->mime_score = 0; } } @@ -787,13 +800,15 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_ddev { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; - wl_data_offer_accept(wl->dnd_offer, time, wl->dnd_mime_type); + struct vo_wayland_data_offer *o = wl->dnd_offer; + wl_data_offer_accept(o->offer, time, o->mime_type); } 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; int pipefd[2]; @@ -803,12 +818,12 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) } if (wl->opts->drag_and_drop != -2) { - MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", wl->dnd_mime_type); - wl_data_offer_receive(wl->dnd_offer, wl->dnd_mime_type, pipefd[1]); + MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", o->mime_type); + wl_data_offer_receive(o->offer, o->mime_type, pipefd[1]); } close(pipefd[1]); - wl->dnd_fd = pipefd[0]; + o->fd = pipefd[0]; } static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev, @@ -816,10 +831,11 @@ 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->dnd_offer; - if (wl->dnd_offer) { - wl_data_offer_destroy(wl->dnd_offer); - wl->dnd_offer = NULL; + if (o->offer) { + wl_data_offer_destroy(o->offer); + o->offer = NULL; MP_VERBOSE(wl, "Received a new DND offer. Releasing the previous offer.\n"); } @@ -1830,21 +1846,23 @@ static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *heigh } } -static void free_dnd_data(struct vo_wayland_state *wl) +static void destroy_offer(struct vo_wayland_data_offer *o) { - // caller should close wl->dnd_fd if appropriate - - wl->dnd_action = -1; - TA_FREEP(&wl->dnd_mime_type); - wl->dnd_mime_score = 0; + TA_FREEP(&o->mime_type); + if (o->fd != -1) + close(o->fd); + if (o->offer) + wl_data_offer_destroy(o->offer); + *o = (struct vo_wayland_data_offer){.fd = -1, .action = -1}; } static void check_dnd_fd(struct vo_wayland_state *wl) { - if (wl->dnd_fd == -1) + struct vo_wayland_data_offer *o = wl->dnd_offer; + if (o->fd == -1) return; - struct pollfd fdp = { wl->dnd_fd, POLLIN | POLLHUP, 0 }; + struct pollfd fdp = { o->fd, POLLIN | POLLHUP, 0 }; if (poll(&fdp, 1, 0) <= 0) return; @@ -1856,7 +1874,7 @@ static void check_dnd_fd(struct vo_wayland_state *wl) }; while (1) { - data_read = read(wl->dnd_fd, file_list.start + file_list.len, chunk_size); + data_read = read(o->fd, file_list.start + file_list.len, chunk_size); if (data_read == -1 && errno == EINTR) continue; else if (data_read <= 0) @@ -1871,24 +1889,24 @@ static void check_dnd_fd(struct vo_wayland_state *wl) } else { MP_VERBOSE(wl, "Read %zu bytes from the DND fd\n", file_list.len); - if (wl->dnd_offer) - wl_data_offer_finish(wl->dnd_offer); + if (o->offer) + wl_data_offer_finish(o->offer); - assert(wl->dnd_action >= 0); - mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type, - file_list, wl->dnd_action); + assert(o->action >= 0); + mp_event_drop_mime_data(wl->vo->input_ctx, o->mime_type, + file_list, o->action); } talloc_free(file_list.start); - free_dnd_data(wl); + free_offer_data(wl->dnd_offer); } if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) { - if (wl->dnd_action >= 0) + if (o->action >= 0) MP_VERBOSE(wl, "DND aborted (hang up or error)\n"); - free_dnd_data(wl); - close(wl->dnd_fd); - wl->dnd_fd = -1; + free_offer_data(wl->dnd_offer); + close(o->fd); + o->fd = -1; } } @@ -3040,7 +3058,7 @@ bool vo_wayland_init(struct vo *vo) .scaling = WAYLAND_SCALE_FACTOR, .wakeup_pipe = {-1, -1}, .display_fd = -1, - .dnd_fd = -1, + .dnd_offer = talloc_zero(wl, struct vo_wayland_data_offer), .cursor_visible = true, .opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts), }; @@ -3269,9 +3287,7 @@ void vo_wayland_uninit(struct vo *vo) if (!wl) return; - if (wl->dnd_fd != -1) - close(wl->dnd_fd); - free_dnd_data(wl); + destroy_offer(wl->dnd_offer); mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); @@ -3313,9 +3329,6 @@ void vo_wayland_uninit(struct vo *vo) if (wl->dnd_devman) wl_data_device_manager_destroy(wl->dnd_devman); - if (wl->dnd_offer) - wl_data_offer_destroy(wl->dnd_offer); - if (wl->fback_pool) clean_feedback_pool(wl->fback_pool); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index abce364cd5372..38ee60222e40b 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -27,6 +27,7 @@ struct compositor_format; struct vo_wayland_seat; struct vo_wayland_tranche; +struct vo_wayland_data_offer; struct drm_format { uint32_t format; @@ -160,13 +161,9 @@ struct vo_wayland_state { struct wl_list seat_list; struct xkb_context *xkb_context; - /* DND */ + /* Data offer */ struct wl_data_device_manager *dnd_devman; - struct wl_data_offer *dnd_offer; - int dnd_action; // actually enum mp_dnd_action - char *dnd_mime_type; - int dnd_fd; - int dnd_mime_score; + struct vo_wayland_data_offer *dnd_offer; /* Cursor */ struct wl_cursor_theme *cursor_theme; From 5759f8a2c1b5d426e177f039b3877b83f3c29b7f Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:01:12 -0500 Subject: [PATCH 2/3] video/out/wayland_common: fix dnd with focus follow mouse Whenever mpv window gains focus, a new data offer is sent for the selection. However, mpv currently treats it the same as dnd data offers, which results in wrong handling. One bug which results from this is when focus follow mouse is enabled, dropping the dnd file results in the window being focused and selection offer being sent, freeing the existing dnd offer. This results in dnd being broken on at least GTK3. Fix this by separating selection and dnd offer handling. Since there is no way to know whether an offer introduced by data_device_handle_data_offer is a selection or dnd offer, make it pending, and move them once the identity is confirmed. --- video/out/wayland_common.c | 54 ++++++++++++++++++++++---------------- video/out/wayland_common.h | 4 ++- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 14352dd5b01cd..1251cea79131a 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -259,6 +259,7 @@ static void set_surface_scaling(struct vo_wayland_state *wl); static void update_output_scaling(struct vo_wayland_state *wl); static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, struct mp_rect old_output_geometry); +static void destroy_offer(struct vo_wayland_data_offer *o); /* Wayland listener boilerplate */ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, @@ -696,12 +697,12 @@ 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->dnd_offer; + struct vo_wayland_data_offer *o = wl->pending_offer; int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type); - if (score > o->mime_score && wl->opts->drag_and_drop != -2) { + if (o->offer && score > o->mime_score && wl->opts->drag_and_drop != -2) { o->mime_score = score; talloc_replace(wl, o->mime_type, mime_type); - MP_VERBOSE(wl, "Given DND offer with mime type %s\n", o->mime_type); + MP_VERBOSE(wl, "Given data offer with mime type %s\n", o->mime_type); } } @@ -743,9 +744,8 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; - struct vo_wayland_data_offer *o = wl->dnd_offer; - if (o->offer) - wl_data_offer_destroy(o->offer); + struct vo_wayland_data_offer *o = wl->pending_offer; + destroy_offer(o); o->offer = id; wl_data_offer_add_listener(id, &data_offer_listener, s); @@ -758,12 +758,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->dnd_offer; + struct vo_wayland_data_offer *o = wl->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; 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, @@ -831,14 +835,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->dnd_offer; - - if (o->offer) { - wl_data_offer_destroy(o->offer); - o->offer = NULL; - MP_VERBOSE(wl, "Received a new DND offer. Releasing the previous offer.\n"); + struct vo_wayland_data_offer *o = wl->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); + 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}; } static const struct wl_data_device_listener data_device_listener = { @@ -1690,7 +1698,7 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) { ver = 3; - wl->dnd_devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, ver); + wl->devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, ver); } if (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) { @@ -1898,15 +1906,13 @@ static void check_dnd_fd(struct vo_wayland_state *wl) } talloc_free(file_list.start); - free_offer_data(wl->dnd_offer); + destroy_offer(o); } if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) { if (o->action >= 0) MP_VERBOSE(wl, "DND aborted (hang up or error)\n"); - free_offer_data(wl->dnd_offer); - close(o->fd); - o->fd = -1; + destroy_offer(o); } } @@ -3058,7 +3064,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), }; @@ -3148,10 +3156,10 @@ bool vo_wayland_init(struct vo *vo) } #endif - if (wl->dnd_devman) { + if (wl->devman) { struct vo_wayland_seat *seat; wl_list_for_each(seat, &wl->seat_list, link) { - seat->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, seat->seat); + seat->dnd_ddev = wl_data_device_manager_get_data_device(wl->devman, seat->seat); wl_data_device_add_listener(seat->dnd_ddev, &data_device_listener, seat); } } else { @@ -3287,7 +3295,9 @@ 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); @@ -3326,8 +3336,8 @@ void vo_wayland_uninit(struct vo *vo) if (wl->content_type_manager) wp_content_type_manager_v1_destroy(wl->content_type_manager); - if (wl->dnd_devman) - wl_data_device_manager_destroy(wl->dnd_devman); + if (wl->devman) + wl_data_device_manager_destroy(wl->devman); if (wl->fback_pool) clean_feedback_pool(wl->fback_pool); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 38ee60222e40b..e2ae46da85d0f 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -162,8 +162,10 @@ struct vo_wayland_state { struct xkb_context *xkb_context; /* Data offer */ - struct wl_data_device_manager *dnd_devman; + 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; /* Cursor */ struct wl_cursor_theme *cursor_theme; From 3fda7c8db3e1b44ff84e9133d48d1ae9369f3142 Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:31:05 -0500 Subject: [PATCH 3/3] video/out/wayland_common: initialize offer fd --- video/out/wayland_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 1251cea79131a..18223f786afdb 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -3070,6 +3070,7 @@ bool vo_wayland_init(struct vo *vo) .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);