mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-02-14 04:28:22 -05:00
xwayland: start drag on dnd selection notify
This commit is contained in:
parent
7d18812735
commit
0207105291
8 changed files with 170 additions and 83 deletions
|
|
@ -221,4 +221,8 @@ void wlr_data_source_dnd_finish(struct wlr_data_source *source);
|
|||
void wlr_data_source_dnd_action(struct wlr_data_source *source,
|
||||
enum wl_data_device_manager_dnd_action action);
|
||||
|
||||
struct wlr_drag *wlr_seat_client_start_grab(struct wlr_seat_client *client,
|
||||
struct wlr_data_source *source, struct wlr_surface *icon_surface,
|
||||
struct wlr_touch_point *point);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ struct wlr_seat {
|
|||
// `drag` goes away before `drag_source`, when the implicit grab ends
|
||||
struct wlr_drag *drag;
|
||||
struct wlr_data_source *drag_source;
|
||||
uint32_t drag_serial;
|
||||
|
||||
struct wlr_seat_pointer_state pointer_state;
|
||||
struct wlr_seat_keyboard_state keyboard_state;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef XWAYLAND_SELECTION_H
|
||||
#define XWAYLAND_SELECTION_H
|
||||
|
||||
#include <wlr/types/wlr_data_device.h>
|
||||
#include <wlr/types/wlr_primary_selection.h>
|
||||
#include <xcb/xfixes.h>
|
||||
|
||||
#define INCR_CHUNK_SIZE (64 * 1024)
|
||||
|
|
@ -39,6 +41,18 @@ struct wlr_xwm_selection {
|
|||
struct wl_list outgoing;
|
||||
};
|
||||
|
||||
struct wlr_xwayland_data_source {
|
||||
struct wlr_data_source base;
|
||||
struct wlr_xwm_selection *selection;
|
||||
struct wl_array mime_types_atoms;
|
||||
};
|
||||
|
||||
struct wlr_xwayland_primary_selection_source {
|
||||
struct wlr_primary_selection_source base;
|
||||
struct wlr_xwm_selection *selection;
|
||||
struct wl_array mime_types_atoms;
|
||||
};
|
||||
|
||||
void xwm_selection_transfer_remove_source(
|
||||
struct wlr_xwm_selection_transfer *transfer);
|
||||
void xwm_selection_transfer_close_source_fd(
|
||||
|
|
@ -63,6 +77,10 @@ int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm,
|
|||
bool data_source_is_xwayland(struct wlr_data_source *wlr_source);
|
||||
bool primary_selection_source_is_xwayland(
|
||||
struct wlr_primary_selection_source *wlr_source);
|
||||
struct wlr_xwayland_data_source *xwayland_data_source_create(
|
||||
struct wlr_xwm_selection *selection);
|
||||
struct wlr_xwayland_data_source *xwayland_data_source_from_wlr_data_source(
|
||||
struct wlr_data_source *wlr_source);
|
||||
|
||||
void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag);
|
||||
int xwm_dnd_handle_xfixes_selection_notify(struct wlr_xwm *xwm,
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ struct wlr_xwm {
|
|||
struct wl_list surfaces; // wlr_xwayland_surface::link
|
||||
struct wl_list unpaired_surfaces; // wlr_xwayland_surface::unpaired_link
|
||||
|
||||
struct wlr_drag *incoming_drag;
|
||||
struct wlr_drag *drag;
|
||||
struct wlr_xwayland_surface *drag_focus;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ static void drag_set_focus(struct wlr_drag *drag,
|
|||
}
|
||||
|
||||
struct wlr_seat_client *focus_client = wlr_seat_client_for_wl_client(
|
||||
drag->seat_client->seat, wl_resource_get_client(surface->resource));
|
||||
drag->seat, wl_resource_get_client(surface->resource));
|
||||
if (!focus_client) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -80,8 +80,7 @@ static void drag_set_focus(struct wlr_drag *drag,
|
|||
}
|
||||
|
||||
if (!wl_list_empty(&focus_client->data_devices)) {
|
||||
uint32_t serial =
|
||||
wl_display_next_serial(drag->seat_client->seat->display);
|
||||
uint32_t serial = wl_display_next_serial(drag->seat->display);
|
||||
struct wl_resource *resource;
|
||||
wl_resource_for_each(resource, &focus_client->data_devices) {
|
||||
wl_data_device_send_enter(resource, serial, surface->resource,
|
||||
|
|
@ -384,54 +383,33 @@ static void seat_handle_drag_source_destroy(struct wl_listener *listener,
|
|||
seat->drag_source = NULL;
|
||||
}
|
||||
|
||||
bool seat_client_start_drag(struct wlr_seat_client *client,
|
||||
struct wlr_drag *wlr_seat_client_start_grab(struct wlr_seat_client *client,
|
||||
struct wlr_data_source *source, struct wlr_surface *icon_surface,
|
||||
struct wlr_surface *origin, uint32_t serial) {
|
||||
struct wlr_touch_point *point) {
|
||||
struct wlr_seat *seat = client->seat;
|
||||
|
||||
struct wlr_drag *drag = calloc(1, sizeof(struct wlr_drag));
|
||||
if (drag == NULL) {
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
drag->seat = seat;
|
||||
wl_signal_init(&drag->events.focus);
|
||||
wl_signal_init(&drag->events.motion);
|
||||
wl_signal_init(&drag->events.drop);
|
||||
wl_signal_init(&drag->events.destroy);
|
||||
|
||||
struct wlr_seat *seat = client->seat;
|
||||
drag->seat = seat;
|
||||
|
||||
drag->is_pointer_grab = !wl_list_empty(&client->pointers) &&
|
||||
seat->pointer_state.button_count == 1 &&
|
||||
seat->pointer_state.grab_serial == serial &&
|
||||
seat->pointer_state.focused_surface &&
|
||||
seat->pointer_state.focused_surface == origin;
|
||||
|
||||
bool is_touch_grab = !wl_list_empty(&client->touches) &&
|
||||
wlr_seat_touch_num_points(seat) == 1 &&
|
||||
seat->touch_state.grab_serial == serial;
|
||||
|
||||
// set in the iteration
|
||||
struct wlr_touch_point *point = NULL;
|
||||
if (is_touch_grab) {
|
||||
wl_list_for_each(point, &seat->touch_state.touch_points, link) {
|
||||
is_touch_grab = point->surface && point->surface == origin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!drag->is_pointer_grab && !is_touch_grab) {
|
||||
free(drag);
|
||||
return true;
|
||||
if (point == NULL) {
|
||||
drag->is_pointer_grab = true;
|
||||
}
|
||||
|
||||
if (icon_surface) {
|
||||
int32_t touch_id = (point ? point->touch_id : 0);
|
||||
int32_t touch_id = point ? point->touch_id : 0;
|
||||
struct wlr_drag_icon *icon =
|
||||
drag_icon_create(icon_surface, client, drag->is_pointer_grab,
|
||||
touch_id);
|
||||
if (!icon) {
|
||||
free(drag);
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
drag->icon = icon;
|
||||
|
|
@ -446,6 +424,7 @@ bool seat_client_start_drag(struct wlr_seat_client *client,
|
|||
}
|
||||
|
||||
drag->seat_client = client;
|
||||
|
||||
drag->pointer_grab.data = drag;
|
||||
drag->pointer_grab.interface = &data_device_pointer_drag_interface;
|
||||
|
||||
|
|
@ -468,7 +447,6 @@ bool seat_client_start_drag(struct wlr_seat_client *client,
|
|||
}
|
||||
|
||||
seat->drag = drag; // TODO: unset this thing somewhere
|
||||
seat->drag_serial = serial;
|
||||
|
||||
seat->drag_source = source;
|
||||
if (source != NULL) {
|
||||
|
|
@ -477,6 +455,38 @@ bool seat_client_start_drag(struct wlr_seat_client *client,
|
|||
}
|
||||
|
||||
wlr_signal_emit_safe(&seat->events.start_drag, drag);
|
||||
|
||||
return true;
|
||||
return drag;
|
||||
}
|
||||
|
||||
bool seat_client_start_drag(struct wlr_seat_client *client,
|
||||
struct wlr_data_source *source, struct wlr_surface *icon_surface,
|
||||
struct wlr_surface *origin, uint32_t serial) {
|
||||
struct wlr_seat *seat = client->seat;
|
||||
|
||||
bool is_pointer_grab = !wl_list_empty(&client->pointers) &&
|
||||
seat->pointer_state.button_count == 1 &&
|
||||
seat->pointer_state.grab_serial == serial &&
|
||||
seat->pointer_state.focused_surface &&
|
||||
seat->pointer_state.focused_surface == origin;
|
||||
|
||||
bool is_touch_grab = !wl_list_empty(&client->touches) &&
|
||||
wlr_seat_touch_num_points(seat) == 1 &&
|
||||
seat->touch_state.grab_serial == serial;
|
||||
|
||||
// set in the iteration
|
||||
struct wlr_touch_point *point = NULL;
|
||||
if (is_touch_grab) {
|
||||
wl_list_for_each(point, &seat->touch_state.touch_points, link) {
|
||||
is_touch_grab = point->surface && point->surface == origin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_pointer_grab && !is_touch_grab) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct wlr_drag *drag = wlr_seat_client_start_grab(client, source,
|
||||
icon_surface, point);
|
||||
return drag != NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,22 +237,33 @@ static void xwm_handle_dnd_finished(struct wlr_xwm *xwm,
|
|||
}
|
||||
|
||||
static bool xwm_add_atom_to_mime_types(struct wlr_xwm *xwm,
|
||||
struct wl_array *mime_types, xcb_atom_t atom) {
|
||||
struct wl_array *mime_types, struct wl_array *mime_types_atoms,
|
||||
xcb_atom_t atom) {
|
||||
char *mime_type = xwm_mime_type_from_atom(xwm, atom);
|
||||
if (mime_type == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char **mime_type_ptr =
|
||||
wl_array_add(mime_types, sizeof(*mime_type_ptr));
|
||||
if (mime_type_ptr == NULL) {
|
||||
return false;
|
||||
}
|
||||
*mime_type_ptr = mime_type;
|
||||
|
||||
xcb_atom_t *mime_type_atom_ptr =
|
||||
wl_array_add(mime_types_atoms, sizeof(*mime_type_atom_ptr));
|
||||
if (mime_type_atom_ptr == NULL) {
|
||||
return false;
|
||||
}
|
||||
*mime_type_atom_ptr = atom;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xwm_dnd_get_mime_types(struct wlr_xwm *xwm,
|
||||
struct wl_array *mime_types, xcb_window_t source) {
|
||||
struct wl_array *mime_types, struct wl_array *mime_types_atoms,
|
||||
xcb_window_t source) {
|
||||
xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn,
|
||||
1, // delete
|
||||
source,
|
||||
|
|
@ -274,7 +285,8 @@ static bool xwm_dnd_get_mime_types(struct wlr_xwm *xwm,
|
|||
|
||||
xcb_atom_t *atoms = xcb_get_property_value(reply);
|
||||
for (uint32_t i = 0; i < reply->value_len; ++i) {
|
||||
if (!xwm_add_atom_to_mime_types(xwm, mime_types, atoms[i])) {
|
||||
if (!xwm_add_atom_to_mime_types(xwm, mime_types, mime_types_atoms,
|
||||
atoms[i])) {
|
||||
wlr_log(L_ERROR, "failed to add MIME type atom to list");
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -307,8 +319,15 @@ static void xwm_handle_dnd_enter(struct wlr_xwm *xwm,
|
|||
return;
|
||||
}
|
||||
|
||||
struct wl_array mime_types;
|
||||
wl_array_init(&mime_types);
|
||||
if (xwm->incoming_drag == NULL) {
|
||||
wlr_log(L_DEBUG, "ignoring XdndEnter client message because "
|
||||
"no xwayland drag is being performed");
|
||||
return;
|
||||
}
|
||||
|
||||
struct wlr_xwayland_data_source *source =
|
||||
xwayland_data_source_from_wlr_data_source(xwm->incoming_drag->source);
|
||||
|
||||
if ((data->data32[1] & 1) == 0) {
|
||||
// Less than 3 MIME types, those are in the message data
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
|
|
@ -316,13 +335,15 @@ static void xwm_handle_dnd_enter(struct wlr_xwm *xwm,
|
|||
if (atom == XCB_ATOM_NONE) {
|
||||
break;
|
||||
}
|
||||
if (!xwm_add_atom_to_mime_types(xwm, &mime_types, atom)) {
|
||||
if (!xwm_add_atom_to_mime_types(xwm, &source->base.mime_types,
|
||||
&source->mime_types_atoms, atom)) {
|
||||
wlr_log(L_ERROR, "failed to add MIME type atom to list");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!xwm_dnd_get_mime_types(xwm, &mime_types, source_window)) {
|
||||
if (!xwm_dnd_get_mime_types(xwm, &source->base.mime_types,
|
||||
&source->mime_types_atoms, source_window)) {
|
||||
wlr_log(L_ERROR, "failed to add MIME type atom to list");
|
||||
}
|
||||
}
|
||||
|
|
@ -358,9 +379,38 @@ int xwm_dnd_handle_xfixes_selection_notify(struct wlr_xwm *xwm,
|
|||
selection->owner = event->owner;
|
||||
|
||||
if (event->owner != XCB_ATOM_NONE) {
|
||||
// TODO: start grab
|
||||
wlr_log(L_INFO, "start grab");
|
||||
|
||||
xcb_map_window(xwm->xcb_conn, xwm->dnd_window);
|
||||
static const uint32_t values[] = { XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(xwm->xcb_conn, xwm->dnd_window,
|
||||
XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
struct wlr_seat *seat = selection->xwm->seat;
|
||||
struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(
|
||||
seat, selection->xwm->xwayland->client);
|
||||
|
||||
struct wlr_xwayland_data_source *source =
|
||||
xwayland_data_source_create(selection);
|
||||
if (source == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
selection->xwm->incoming_drag =
|
||||
wlr_seat_client_start_grab(seat_client, &source->base, NULL, NULL);
|
||||
if (selection->xwm->incoming_drag == NULL) {
|
||||
wlr_log(L_ERROR, "could not start grab");
|
||||
}
|
||||
} else {
|
||||
// TODO: end grab
|
||||
wlr_log(L_INFO, "end grab");
|
||||
|
||||
static const uint32_t values[] = { XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(xwm->xcb_conn, xwm->dnd_window,
|
||||
XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
xcb_unmap_window(xwm->xcb_conn, xwm->dnd_window);
|
||||
|
||||
// TODO: drag_end(selection->xwm->incoming_drag)
|
||||
selection->xwm->incoming_drag = NULL;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -174,12 +174,6 @@ static void source_send(struct wlr_xwm_selection *selection,
|
|||
transfer->source_fd = fd;
|
||||
}
|
||||
|
||||
struct x11_data_source {
|
||||
struct wlr_data_source base;
|
||||
struct wlr_xwm_selection *selection;
|
||||
struct wl_array mime_types_atoms;
|
||||
};
|
||||
|
||||
static const struct wlr_data_source_impl data_source_impl;
|
||||
|
||||
bool data_source_is_xwayland(
|
||||
|
|
@ -187,16 +181,16 @@ bool data_source_is_xwayland(
|
|||
return wlr_source->impl == &data_source_impl;
|
||||
}
|
||||
|
||||
static struct x11_data_source *data_source_from_wlr_data_source(
|
||||
struct wlr_xwayland_data_source *xwayland_data_source_from_wlr_data_source(
|
||||
struct wlr_data_source *wlr_source) {
|
||||
assert(data_source_is_xwayland(wlr_source));
|
||||
return (struct x11_data_source *)wlr_source;
|
||||
return (struct wlr_xwayland_data_source *)wlr_source;
|
||||
}
|
||||
|
||||
static void data_source_send(struct wlr_data_source *wlr_source,
|
||||
const char *mime_type, int32_t fd) {
|
||||
struct x11_data_source *source =
|
||||
data_source_from_wlr_data_source(wlr_source);
|
||||
struct wlr_xwayland_data_source *source =
|
||||
xwayland_data_source_from_wlr_data_source(wlr_source);
|
||||
struct wlr_xwm_selection *selection = source->selection;
|
||||
|
||||
source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms,
|
||||
|
|
@ -204,24 +198,34 @@ static void data_source_send(struct wlr_data_source *wlr_source,
|
|||
}
|
||||
|
||||
static void data_source_cancel(struct wlr_data_source *wlr_source) {
|
||||
struct x11_data_source *source =
|
||||
data_source_from_wlr_data_source(wlr_source);
|
||||
struct wlr_xwayland_data_source *source =
|
||||
xwayland_data_source_from_wlr_data_source(wlr_source);
|
||||
wlr_data_source_finish(&source->base);
|
||||
wl_array_release(&source->mime_types_atoms);
|
||||
free(source);
|
||||
}
|
||||
|
||||
struct wlr_xwayland_data_source *xwayland_data_source_create(
|
||||
struct wlr_xwm_selection *selection) {
|
||||
struct wlr_xwayland_data_source *source =
|
||||
calloc(1, sizeof(struct wlr_xwayland_data_source));
|
||||
if (source == NULL) {
|
||||
wlr_log(L_ERROR, "allocation failed");
|
||||
return NULL;
|
||||
}
|
||||
wlr_data_source_init(&source->base, &data_source_impl);
|
||||
|
||||
source->selection = selection;
|
||||
wl_array_init(&source->mime_types_atoms);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
static const struct wlr_data_source_impl data_source_impl = {
|
||||
.send = data_source_send,
|
||||
.cancel = data_source_cancel,
|
||||
};
|
||||
|
||||
struct x11_primary_selection_source {
|
||||
struct wlr_primary_selection_source base;
|
||||
struct wlr_xwm_selection *selection;
|
||||
struct wl_array mime_types_atoms;
|
||||
};
|
||||
|
||||
static void primary_selection_source_cancel(
|
||||
struct wlr_primary_selection_source *wlr_source);
|
||||
|
||||
|
|
@ -233,8 +237,8 @@ bool primary_selection_source_is_xwayland(
|
|||
static void primary_selection_source_send(
|
||||
struct wlr_primary_selection_source *wlr_source, const char *mime_type,
|
||||
int32_t fd) {
|
||||
struct x11_primary_selection_source *source =
|
||||
(struct x11_primary_selection_source *)wlr_source;
|
||||
struct wlr_xwayland_primary_selection_source *source =
|
||||
(struct wlr_xwayland_primary_selection_source *)wlr_source;
|
||||
struct wlr_xwm_selection *selection = source->selection;
|
||||
|
||||
source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms,
|
||||
|
|
@ -243,8 +247,8 @@ static void primary_selection_source_send(
|
|||
|
||||
static void primary_selection_source_cancel(
|
||||
struct wlr_primary_selection_source *wlr_source) {
|
||||
struct x11_primary_selection_source *source =
|
||||
(struct x11_primary_selection_source *)wlr_source;
|
||||
struct wlr_xwayland_primary_selection_source *source =
|
||||
(struct wlr_xwayland_primary_selection_source *)wlr_source;
|
||||
wlr_primary_selection_source_finish(&source->base);
|
||||
wl_array_release(&source->mime_types_atoms);
|
||||
free(source);
|
||||
|
|
@ -332,15 +336,11 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) {
|
|||
struct wlr_xwm *xwm = selection->xwm;
|
||||
|
||||
if (selection == &xwm->clipboard_selection) {
|
||||
struct x11_data_source *source =
|
||||
calloc(1, sizeof(struct x11_data_source));
|
||||
struct wlr_xwayland_data_source *source =
|
||||
xwayland_data_source_create(selection);
|
||||
if (source == NULL) {
|
||||
return;
|
||||
}
|
||||
wlr_data_source_init(&source->base, &data_source_impl);
|
||||
|
||||
source->selection = selection;
|
||||
wl_array_init(&source->mime_types_atoms);
|
||||
|
||||
bool ok = source_get_targets(selection, &source->base.mime_types,
|
||||
&source->mime_types_atoms);
|
||||
|
|
@ -351,8 +351,8 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) {
|
|||
wlr_data_source_cancel(&source->base);
|
||||
}
|
||||
} else if (selection == &xwm->primary_selection) {
|
||||
struct x11_primary_selection_source *source =
|
||||
calloc(1, sizeof(struct x11_primary_selection_source));
|
||||
struct wlr_xwayland_primary_selection_source *source =
|
||||
calloc(1, sizeof(struct wlr_xwayland_primary_selection_source));
|
||||
if (source == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -371,8 +371,6 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) {
|
|||
} else {
|
||||
source->base.cancel(&source->base);
|
||||
}
|
||||
} else if (selection == &xwm->dnd_selection) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,15 +440,17 @@ int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm,
|
|||
} else {
|
||||
selection->owner = event->owner;
|
||||
|
||||
// This is our selection window.
|
||||
// We have to use XCB_TIME_CURRENT_TIME when we claim the
|
||||
// selection, so grab the actual timestamp here so we can
|
||||
// answer TIMESTAMP conversion requests correctly.
|
||||
if (event->owner == selection->window) {
|
||||
// This is our selection window.
|
||||
// We have to use XCB_TIME_CURRENT_TIME when we claim the
|
||||
// selection, so grab the actual timestamp here so we can
|
||||
// answer TIMESTAMP conversion requests correctly.
|
||||
selection->timestamp = event->timestamp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is a real X client's selection
|
||||
|
||||
struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
|
||||
transfer->incr = false;
|
||||
// doing this will give a selection notify where we actually handle the sync
|
||||
|
|
|
|||
|
|
@ -289,6 +289,11 @@ static void seat_handle_start_drag(struct wl_listener *listener, void *data) {
|
|||
struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_start_drag);
|
||||
struct wlr_drag *drag = data;
|
||||
|
||||
if (drag && (drag->source == NULL ||
|
||||
!data_source_is_xwayland(drag->source))) {
|
||||
return;
|
||||
}
|
||||
|
||||
xwm_selection_set_owner(&xwm->dnd_selection, drag != NULL);
|
||||
xwm_seat_handle_start_drag(xwm, drag);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue