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