1 #define _XOPEN_SOURCE 700
2 #include <assert.h>
3 #include <stdlib.h>
4 #include <strings.h>
5 #include <unistd.h>
6 #include <wayland-server-core.h>
7 #include <wlr/types/wlr_data_device.h>
8 #include <wlr/types/wlr_seat.h>
9 #include <wlr/util/log.h>
10 #include "types/wlr_data_device.h"
11 #include "util/signal.h"
12
13 static const struct wl_data_offer_interface data_offer_impl;
14
data_offer_from_resource(struct wl_resource * resource)15 static struct wlr_data_offer *data_offer_from_resource(
16 struct wl_resource *resource) {
17 assert(wl_resource_instance_of(resource, &wl_data_offer_interface,
18 &data_offer_impl));
19 return wl_resource_get_user_data(resource);
20 }
21
data_offer_choose_action(struct wlr_data_offer * offer)22 static uint32_t data_offer_choose_action(struct wlr_data_offer *offer) {
23 uint32_t offer_actions, preferred_action = 0;
24 if (wl_resource_get_version(offer->resource) >=
25 WL_DATA_OFFER_ACTION_SINCE_VERSION) {
26 offer_actions = offer->actions;
27 preferred_action = offer->preferred_action;
28 } else {
29 offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
30 }
31
32 uint32_t source_actions;
33 if (offer->source->actions >= 0) {
34 source_actions = offer->source->actions;
35 } else {
36 source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
37 }
38
39 uint32_t available_actions = offer_actions & source_actions;
40 if (!available_actions) {
41 return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
42 }
43
44 if (offer->source->compositor_action & available_actions) {
45 return offer->source->compositor_action;
46 }
47
48 // If the dest side has a preferred DnD action, use it
49 if ((preferred_action & available_actions) != 0) {
50 return preferred_action;
51 }
52
53 // Use the first found action, in bit order
54 return 1 << (ffs(available_actions) - 1);
55 }
56
data_offer_update_action(struct wlr_data_offer * offer)57 void data_offer_update_action(struct wlr_data_offer *offer) {
58 assert(offer->type == WLR_DATA_OFFER_DRAG);
59
60 uint32_t action = data_offer_choose_action(offer);
61 if (offer->source->current_dnd_action == action) {
62 return;
63 }
64 offer->source->current_dnd_action = action;
65
66 if (offer->in_ask) {
67 return;
68 }
69
70 wlr_data_source_dnd_action(offer->source, action);
71
72 if (wl_resource_get_version(offer->resource) >=
73 WL_DATA_OFFER_ACTION_SINCE_VERSION) {
74 wl_data_offer_send_action(offer->resource, action);
75 }
76 }
77
data_offer_handle_accept(struct wl_client * client,struct wl_resource * resource,uint32_t serial,const char * mime_type)78 static void data_offer_handle_accept(struct wl_client *client,
79 struct wl_resource *resource, uint32_t serial, const char *mime_type) {
80 struct wlr_data_offer *offer = data_offer_from_resource(resource);
81 if (offer == NULL) {
82 return;
83 }
84
85 if (offer->type != WLR_DATA_OFFER_DRAG) {
86 wlr_log(WLR_DEBUG, "Ignoring wl_data_offer.accept request on a "
87 "non-drag-and-drop offer");
88 return;
89 }
90
91 wlr_data_source_accept(offer->source, serial, mime_type);
92 }
93
data_offer_handle_receive(struct wl_client * client,struct wl_resource * resource,const char * mime_type,int32_t fd)94 static void data_offer_handle_receive(struct wl_client *client,
95 struct wl_resource *resource, const char *mime_type, int32_t fd) {
96 struct wlr_data_offer *offer = data_offer_from_resource(resource);
97 if (offer == NULL) {
98 close(fd);
99 return;
100 }
101
102 wlr_data_source_send(offer->source, mime_type, fd);
103 }
104
data_offer_source_dnd_finish(struct wlr_data_offer * offer)105 static void data_offer_source_dnd_finish(struct wlr_data_offer *offer) {
106 struct wlr_data_source *source = offer->source;
107 if (source->actions < 0) {
108 return;
109 }
110
111 if (offer->in_ask) {
112 wlr_data_source_dnd_action(source, source->current_dnd_action);
113 }
114
115 wlr_data_source_dnd_finish(source);
116 }
117
data_offer_handle_destroy(struct wl_client * client,struct wl_resource * resource)118 static void data_offer_handle_destroy(struct wl_client *client,
119 struct wl_resource *resource) {
120 wl_resource_destroy(resource);
121 }
122
data_offer_handle_finish(struct wl_client * client,struct wl_resource * resource)123 static void data_offer_handle_finish(struct wl_client *client,
124 struct wl_resource *resource) {
125 struct wlr_data_offer *offer = data_offer_from_resource(resource);
126 if (offer == NULL) {
127 return;
128 }
129
130 // TODO: also fail while we have a drag-and-drop grab
131 if (offer->type != WLR_DATA_OFFER_DRAG) {
132 wl_resource_post_error(offer->resource,
133 WL_DATA_OFFER_ERROR_INVALID_FINISH, "Offer is not drag-and-drop");
134 return;
135 }
136 if (!offer->source->accepted) {
137 wl_resource_post_error(offer->resource,
138 WL_DATA_OFFER_ERROR_INVALID_FINISH, "Premature finish request");
139 return;
140 }
141 enum wl_data_device_manager_dnd_action action =
142 offer->source->current_dnd_action;
143 if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE ||
144 action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) {
145 wl_resource_post_error(offer->resource,
146 WL_DATA_OFFER_ERROR_INVALID_FINISH,
147 "Offer finished with an invalid action");
148 return;
149 }
150
151 data_offer_source_dnd_finish(offer);
152 data_offer_destroy(offer);
153 }
154
data_offer_handle_set_actions(struct wl_client * client,struct wl_resource * resource,uint32_t actions,uint32_t preferred_action)155 static void data_offer_handle_set_actions(struct wl_client *client,
156 struct wl_resource *resource, uint32_t actions,
157 uint32_t preferred_action) {
158 struct wlr_data_offer *offer = data_offer_from_resource(resource);
159 if (offer == NULL) {
160 return;
161 }
162
163 if (actions & ~DATA_DEVICE_ALL_ACTIONS) {
164 wl_resource_post_error(offer->resource,
165 WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK,
166 "invalid action mask %x", actions);
167 return;
168 }
169
170 if (preferred_action && (!(preferred_action & actions) ||
171 __builtin_popcount(preferred_action) > 1)) {
172 wl_resource_post_error(offer->resource,
173 WL_DATA_OFFER_ERROR_INVALID_ACTION,
174 "invalid action %x", preferred_action);
175 return;
176 }
177
178 if (offer->type != WLR_DATA_OFFER_DRAG) {
179 wl_resource_post_error(offer->resource,
180 WL_DATA_OFFER_ERROR_INVALID_OFFER,
181 "set_action can only be sent to drag-and-drop offers");
182 return;
183 }
184
185 offer->actions = actions;
186 offer->preferred_action = preferred_action;
187
188 data_offer_update_action(offer);
189 }
190
data_offer_destroy(struct wlr_data_offer * offer)191 void data_offer_destroy(struct wlr_data_offer *offer) {
192 if (offer == NULL) {
193 return;
194 }
195
196 wl_list_remove(&offer->source_destroy.link);
197 wl_list_remove(&offer->link);
198
199 if (offer->type == WLR_DATA_OFFER_DRAG && offer->source) {
200 // If the drag destination has version < 3, wl_data_offer.finish
201 // won't be called, so do this here as a safety net, because
202 // we still want the version >= 3 drag source to be happy.
203 if (wl_resource_get_version(offer->resource) <
204 WL_DATA_OFFER_ACTION_SINCE_VERSION) {
205 data_offer_source_dnd_finish(offer);
206 } else if (offer->source->impl->dnd_finish) {
207 wlr_data_source_destroy(offer->source);
208 }
209 }
210
211 // Make the resource inert
212 wl_resource_set_user_data(offer->resource, NULL);
213
214 free(offer);
215 }
216
217 static const struct wl_data_offer_interface data_offer_impl = {
218 .accept = data_offer_handle_accept,
219 .receive = data_offer_handle_receive,
220 .destroy = data_offer_handle_destroy,
221 .finish = data_offer_handle_finish,
222 .set_actions = data_offer_handle_set_actions,
223 };
224
data_offer_handle_resource_destroy(struct wl_resource * resource)225 static void data_offer_handle_resource_destroy(struct wl_resource *resource) {
226 struct wlr_data_offer *offer = data_offer_from_resource(resource);
227 data_offer_destroy(offer);
228 }
229
data_offer_handle_source_destroy(struct wl_listener * listener,void * data)230 static void data_offer_handle_source_destroy(struct wl_listener *listener,
231 void *data) {
232 struct wlr_data_offer *offer =
233 wl_container_of(listener, offer, source_destroy);
234 // Prevent data_offer_destroy from destroying the source again
235 offer->source = NULL;
236 data_offer_destroy(offer);
237 }
238
data_offer_create(struct wl_resource * device_resource,struct wlr_data_source * source,enum wlr_data_offer_type type)239 struct wlr_data_offer *data_offer_create(struct wl_resource *device_resource,
240 struct wlr_data_source *source, enum wlr_data_offer_type type) {
241 struct wlr_seat_client *seat_client =
242 seat_client_from_data_device_resource(device_resource);
243 assert(seat_client != NULL);
244 assert(source != NULL); // a NULL source means no selection
245
246 struct wlr_data_offer *offer = calloc(1, sizeof(struct wlr_data_offer));
247 if (offer == NULL) {
248 return NULL;
249 }
250 offer->source = source;
251 offer->type = type;
252
253 struct wl_client *client = wl_resource_get_client(device_resource);
254 uint32_t version = wl_resource_get_version(device_resource);
255 offer->resource =
256 wl_resource_create(client, &wl_data_offer_interface, version, 0);
257 if (offer->resource == NULL) {
258 free(offer);
259 return NULL;
260 }
261 wl_resource_set_implementation(offer->resource, &data_offer_impl, offer,
262 data_offer_handle_resource_destroy);
263
264 switch (type) {
265 case WLR_DATA_OFFER_SELECTION:
266 wl_list_insert(&seat_client->seat->selection_offers, &offer->link);
267 break;
268 case WLR_DATA_OFFER_DRAG:
269 wl_list_insert(&seat_client->seat->drag_offers, &offer->link);
270 break;
271 }
272
273 offer->source_destroy.notify = data_offer_handle_source_destroy;
274 wl_signal_add(&source->events.destroy, &offer->source_destroy);
275
276 wl_data_device_send_data_offer(device_resource, offer->resource);
277
278 char **p;
279 wl_array_for_each(p, &source->mime_types) {
280 wl_data_offer_send_offer(offer->resource, *p);
281 }
282
283 return offer;
284 }
285