1 | /* GDK - The GIMP Drawing Kit |
2 | * Copyright (C) 2017 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gdkclipboardprivate.h" |
21 | #include "gdkclipboard-wayland.h" |
22 | |
23 | #include "gdkcontentformats.h" |
24 | #include "gdkintl.h" |
25 | #include "gdkprivate-wayland.h" |
26 | #include "gdk-private.h" |
27 | |
28 | #include <glib-unix.h> |
29 | #include <gio/gunixinputstream.h> |
30 | #include <gio/gunixoutputstream.h> |
31 | |
32 | typedef struct _GdkWaylandClipboardClass GdkWaylandClipboardClass; |
33 | |
34 | struct _GdkWaylandClipboard |
35 | { |
36 | GdkClipboard parent; |
37 | |
38 | struct wl_data_offer *offer; |
39 | GdkContentFormats *offer_formats; |
40 | |
41 | struct wl_data_source *source; |
42 | }; |
43 | |
44 | struct _GdkWaylandClipboardClass |
45 | { |
46 | GdkClipboardClass parent_class; |
47 | }; |
48 | |
49 | G_DEFINE_TYPE (GdkWaylandClipboard, gdk_wayland_clipboard, GDK_TYPE_CLIPBOARD) |
50 | |
51 | static void |
52 | gdk_wayland_clipboard_discard_offer (GdkWaylandClipboard *cb) |
53 | { |
54 | g_clear_pointer (&cb->offer_formats, gdk_content_formats_unref); |
55 | g_clear_pointer (&cb->offer, wl_data_offer_destroy); |
56 | } |
57 | |
58 | static void |
59 | gdk_wayland_clipboard_discard_source (GdkWaylandClipboard *cb) |
60 | { |
61 | g_clear_pointer (&cb->source, wl_data_source_destroy); |
62 | } |
63 | |
64 | static void |
65 | gdk_wayland_clipboard_finalize (GObject *object) |
66 | { |
67 | GdkWaylandClipboard *cb = GDK_WAYLAND_CLIPBOARD (object); |
68 | |
69 | gdk_wayland_clipboard_discard_offer (cb); |
70 | gdk_wayland_clipboard_discard_source (cb); |
71 | |
72 | G_OBJECT_CLASS (gdk_wayland_clipboard_parent_class)->finalize (object); |
73 | } |
74 | |
75 | static void |
76 | gdk_wayland_clipboard_data_source_target (void *data, |
77 | struct wl_data_source *source, |
78 | const char *mime_type) |
79 | { |
80 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), CLIPBOARD, g_message ("%p: Huh? data_source.target() events?" , data)); |
81 | } |
82 | |
83 | static void |
84 | gdk_wayland_clipboard_write_done (GObject *clipboard, |
85 | GAsyncResult *result, |
86 | gpointer user_data) |
87 | { |
88 | GError *error = NULL; |
89 | |
90 | if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, error: &error)) |
91 | { |
92 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), CLIPBOARD, g_message ("%p: failed to write stream: %s" , clipboard, error->message)); |
93 | g_error_free (error); |
94 | } |
95 | } |
96 | |
97 | static void |
98 | gdk_wayland_clipboard_data_source_send (void *data, |
99 | struct wl_data_source *source, |
100 | const char *mime_type, |
101 | int32_t fd) |
102 | { |
103 | GdkWaylandClipboard *cb = GDK_WAYLAND_CLIPBOARD (data); |
104 | GOutputStream *stream; |
105 | |
106 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), CLIPBOARD, g_message ("%p: data source send request for %s on fd %d" , |
107 | source, mime_type, fd)); |
108 | |
109 | mime_type = gdk_intern_mime_type (string: mime_type); |
110 | stream = g_unix_output_stream_new (fd, TRUE); |
111 | |
112 | gdk_clipboard_write_async (GDK_CLIPBOARD (cb), |
113 | mime_type, |
114 | stream, |
115 | G_PRIORITY_DEFAULT, |
116 | NULL, |
117 | callback: gdk_wayland_clipboard_write_done, |
118 | user_data: cb); |
119 | g_object_unref (object: stream); |
120 | } |
121 | |
122 | static void |
123 | gdk_wayland_clipboard_data_source_cancelled (void *data, |
124 | struct wl_data_source *source) |
125 | { |
126 | GdkWaylandClipboard *cb = GDK_WAYLAND_CLIPBOARD (data); |
127 | |
128 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), CLIPBOARD, g_message ("%p: data source cancelled" , data)); |
129 | |
130 | if (cb->source == source) |
131 | { |
132 | gdk_wayland_clipboard_discard_source (cb); |
133 | gdk_wayland_clipboard_claim_remote (cb, NULL, formats: gdk_content_formats_new (NULL, n_mime_types: 0)); |
134 | } |
135 | } |
136 | |
137 | static void |
138 | gdk_wayland_clipboard_data_source_dnd_drop_performed (void *data, |
139 | struct wl_data_source *source) |
140 | { |
141 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), |
142 | CLIPBOARD, g_message ("%p: Huh? data_source.dnd_drop_performed() events?" , data)); |
143 | } |
144 | |
145 | static void |
146 | gdk_wayland_clipboard_data_source_dnd_finished (void *data, |
147 | struct wl_data_source *source) |
148 | { |
149 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), |
150 | CLIPBOARD, g_message ("%p: Huh? data_source.dnd_finished() events?" , data)); |
151 | } |
152 | |
153 | static void |
154 | gdk_wayland_clipboard_data_source_action (void *data, |
155 | struct wl_data_source *source, |
156 | uint32_t action) |
157 | { |
158 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), |
159 | CLIPBOARD, g_message ("%p: Huh? data_source.action() events?" , data)); |
160 | } |
161 | |
162 | static const struct wl_data_source_listener data_source_listener = { |
163 | gdk_wayland_clipboard_data_source_target, |
164 | gdk_wayland_clipboard_data_source_send, |
165 | gdk_wayland_clipboard_data_source_cancelled, |
166 | gdk_wayland_clipboard_data_source_dnd_drop_performed, |
167 | gdk_wayland_clipboard_data_source_dnd_finished, |
168 | gdk_wayland_clipboard_data_source_action, |
169 | }; |
170 | |
171 | static gboolean |
172 | gdk_wayland_clipboard_claim (GdkClipboard *clipboard, |
173 | GdkContentFormats *formats, |
174 | gboolean local, |
175 | GdkContentProvider *content) |
176 | { |
177 | GdkWaylandClipboard *cb = GDK_WAYLAND_CLIPBOARD (clipboard); |
178 | |
179 | if (local) |
180 | { |
181 | GdkWaylandDisplay *wayland_display = GDK_WAYLAND_DISPLAY (gdk_clipboard_get_display (clipboard)); |
182 | GdkDevice *device; |
183 | const char * const *mime_types; |
184 | gsize i, n_mime_types; |
185 | |
186 | gdk_wayland_clipboard_discard_offer (cb); |
187 | gdk_wayland_clipboard_discard_source (cb); |
188 | |
189 | cb->source = wl_data_device_manager_create_data_source (wl_data_device_manager: wayland_display->data_device_manager); |
190 | wl_data_source_add_listener (wl_data_source: cb->source, listener: &data_source_listener, data: cb); |
191 | |
192 | mime_types = gdk_content_formats_get_mime_types (formats, n_mime_types: &n_mime_types); |
193 | for (i = 0; i < n_mime_types; i++) |
194 | { |
195 | wl_data_source_offer (wl_data_source: cb->source, mime_type: mime_types[i]); |
196 | } |
197 | |
198 | device = gdk_seat_get_pointer (seat: gdk_display_get_default_seat (GDK_DISPLAY (wayland_display))); |
199 | gdk_wayland_device_set_selection (gdk_device: device, source: cb->source); |
200 | } |
201 | |
202 | return GDK_CLIPBOARD_CLASS (gdk_wayland_clipboard_parent_class)->claim (clipboard, formats, local, content); |
203 | } |
204 | |
205 | static void |
206 | gdk_wayland_clipboard_read_async (GdkClipboard *clipboard, |
207 | GdkContentFormats *formats, |
208 | int io_priority, |
209 | GCancellable *cancellable, |
210 | GAsyncReadyCallback callback, |
211 | gpointer user_data) |
212 | { |
213 | GdkWaylandClipboard *cb = GDK_WAYLAND_CLIPBOARD (clipboard); |
214 | GInputStream *stream; |
215 | const char *mime_type; |
216 | int pipe_fd[2]; |
217 | GError *error = NULL; |
218 | GTask *task; |
219 | |
220 | task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data); |
221 | g_task_set_priority (task, priority: io_priority); |
222 | g_task_set_source_tag (task, gdk_wayland_clipboard_read_async); |
223 | |
224 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, char *s = gdk_content_formats_to_string (formats); |
225 | g_message ("%p: read for %s" , cb, s); |
226 | g_free (s); ); |
227 | mime_type = gdk_content_formats_match_mime_type (first: formats, second: cb->offer_formats); |
228 | if (mime_type == NULL) |
229 | { |
230 | g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
231 | _("No compatible transfer format found" )); |
232 | return; |
233 | } |
234 | /* offer formats should be empty if we have no offer */ |
235 | g_assert (cb->offer); |
236 | |
237 | g_task_set_task_data (task, task_data: (gpointer) mime_type, NULL); |
238 | |
239 | if (!g_unix_open_pipe (fds: pipe_fd, FD_CLOEXEC, error: &error)) |
240 | { |
241 | g_task_return_error (task, error); |
242 | return; |
243 | } |
244 | |
245 | wl_data_offer_receive (wl_data_offer: cb->offer, mime_type, fd: pipe_fd[1]); |
246 | stream = g_unix_input_stream_new (fd: pipe_fd[0], TRUE); |
247 | close (fd: pipe_fd[1]); |
248 | g_task_return_pointer (task, result: stream, result_destroy: g_object_unref); |
249 | } |
250 | |
251 | static GInputStream * |
252 | gdk_wayland_clipboard_read_finish (GdkClipboard *clipboard, |
253 | GAsyncResult *result, |
254 | const char **out_mime_type, |
255 | GError **error) |
256 | { |
257 | GTask *task; |
258 | |
259 | g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL); |
260 | task = G_TASK (result); |
261 | g_return_val_if_fail (g_task_get_source_tag (task) == gdk_wayland_clipboard_read_async, NULL); |
262 | |
263 | if (out_mime_type) |
264 | *out_mime_type = g_task_get_task_data (task); |
265 | |
266 | return g_task_propagate_pointer (task, error); |
267 | } |
268 | |
269 | static void |
270 | gdk_wayland_clipboard_class_init (GdkWaylandClipboardClass *class) |
271 | { |
272 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
273 | GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class); |
274 | |
275 | object_class->finalize = gdk_wayland_clipboard_finalize; |
276 | |
277 | clipboard_class->claim = gdk_wayland_clipboard_claim; |
278 | clipboard_class->read_async = gdk_wayland_clipboard_read_async; |
279 | clipboard_class->read_finish = gdk_wayland_clipboard_read_finish; |
280 | } |
281 | |
282 | static void |
283 | gdk_wayland_clipboard_init (GdkWaylandClipboard *cb) |
284 | { |
285 | } |
286 | |
287 | GdkClipboard * |
288 | gdk_wayland_clipboard_new (GdkDisplay *display) |
289 | { |
290 | GdkWaylandClipboard *cb; |
291 | |
292 | cb = g_object_new (GDK_TYPE_WAYLAND_CLIPBOARD, |
293 | first_property_name: "display" , display, |
294 | NULL); |
295 | |
296 | return GDK_CLIPBOARD (cb); |
297 | } |
298 | |
299 | void |
300 | gdk_wayland_clipboard_claim_remote (GdkWaylandClipboard *cb, |
301 | struct wl_data_offer *offer, |
302 | GdkContentFormats *formats) |
303 | { |
304 | g_return_if_fail (GDK_IS_WAYLAND_CLIPBOARD (cb)); |
305 | |
306 | if (cb->source) |
307 | { |
308 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, g_message ("%p: Ignoring clipboard offer for self" , cb)); |
309 | gdk_content_formats_unref (formats); |
310 | return; |
311 | } |
312 | |
313 | gdk_wayland_clipboard_discard_offer (cb); |
314 | |
315 | GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, char *s = gdk_content_formats_to_string (formats); |
316 | g_message ("%p: remote clipboard claim for %s" , cb, s); |
317 | g_free (s); ); |
318 | cb->offer_formats = formats; |
319 | cb->offer = offer; |
320 | |
321 | gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), |
322 | formats: cb->offer_formats); |
323 | } |
324 | |
325 | |