Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-clipboard-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <json-glib/json-glib.h>
10 : : #include <valent.h>
11 : :
12 : : #include "valent-clipboard-plugin.h"
13 : :
14 : :
15 : : struct _ValentClipboardPlugin
16 : : {
17 : : ValentDevicePlugin parent_instance;
18 : :
19 : : ValentClipboard *clipboard;
20 : : unsigned long changed_id;
21 : :
22 : : char *remote_text;
23 : : int64_t remote_timestamp;
24 : : int64_t local_timestamp;
25 : : unsigned int auto_pull : 1;
26 : : unsigned int auto_push : 1;
27 : : };
28 : :
29 [ + + + - ]: 98 : G_DEFINE_FINAL_TYPE (ValentClipboardPlugin, valent_clipboard_plugin, VALENT_TYPE_DEVICE_PLUGIN)
30 : :
31 : :
32 : : /*
33 : : * Local Clipboard
34 : : */
35 : : static void
36 : 2 : valent_clipboard_plugin_clipboard (ValentClipboardPlugin *self,
37 : : const char *content)
38 : : {
39 : 2 : g_autoptr (JsonBuilder) builder = NULL;
40 [ - + - - ]: 2 : g_autoptr (JsonNode) packet = NULL;
41 : :
42 [ + - ]: 2 : g_return_if_fail (VALENT_IS_CLIPBOARD_PLUGIN (self));
43 : :
44 [ + - ]: 2 : if (content == NULL)
45 : : return;
46 : :
47 : 2 : valent_packet_init (&builder, "kdeconnect.clipboard");
48 : 2 : json_builder_set_member_name (builder, "content");
49 : 2 : json_builder_add_string_value (builder, content);
50 : 2 : packet = valent_packet_end (&builder);
51 : :
52 [ + - ]: 2 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
53 : : }
54 : :
55 : : static void
56 : 3 : valent_clipboard_plugin_clipboard_connect (ValentClipboardPlugin *self,
57 : : const char *content,
58 : : int64_t timestamp)
59 : : {
60 : 3 : g_autoptr (JsonBuilder) builder = NULL;
61 [ - + - - ]: 3 : g_autoptr (JsonNode) packet = NULL;
62 : :
63 [ + - ]: 3 : g_return_if_fail (VALENT_IS_CLIPBOARD_PLUGIN (self));
64 : :
65 [ + - ]: 3 : if (content == NULL)
66 : : return;
67 : :
68 : 3 : valent_packet_init (&builder, "kdeconnect.clipboard.connect");
69 : 3 : json_builder_set_member_name (builder, "content");
70 : 3 : json_builder_add_string_value (builder, content);
71 : 3 : json_builder_set_member_name (builder, "timestamp");
72 : 3 : json_builder_add_int_value (builder, timestamp);
73 : 3 : packet = valent_packet_end (&builder);
74 : :
75 [ + - ]: 3 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
76 : : }
77 : :
78 : : static void
79 : 4 : valent_clipboard_read_text_cb (ValentClipboard *clipboard,
80 : : GAsyncResult *result,
81 : : ValentClipboardPlugin *self)
82 : : {
83 : 4 : g_autoptr (GError) error = NULL;
84 [ - + - + ]: 4 : g_autofree char *text = NULL;
85 : :
86 [ + - ]: 4 : g_assert (VALENT_IS_CLIPBOARD (clipboard));
87 [ - + ]: 4 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
88 : :
89 : 4 : text = valent_clipboard_read_text_finish (clipboard, result, &error);
90 : :
91 [ - + ]: 4 : if (error != NULL)
92 : : {
93 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
94 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
95 : :
96 : 0 : return;
97 : : }
98 : :
99 : : /* Skip if the local clipboard is empty, or already synced with the device */
100 [ + - + + ]: 4 : if (text == NULL || g_strcmp0 (self->remote_text, text) == 0)
101 : 2 : return;
102 : :
103 : 2 : valent_clipboard_plugin_clipboard (self, text);
104 : : }
105 : :
106 : : static void
107 : 3 : valent_clipboard_read_text_connect_cb (ValentClipboard *clipboard,
108 : : GAsyncResult *result,
109 : : ValentClipboardPlugin *self)
110 : : {
111 : 3 : g_autofree char *text = NULL;
112 : 3 : g_autoptr (GError) error = NULL;
113 : :
114 [ + - ]: 3 : g_assert (VALENT_IS_CLIPBOARD (clipboard));
115 [ - + ]: 3 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
116 : :
117 : 3 : text = valent_clipboard_read_text_finish (clipboard, result, &error);
118 : :
119 [ - + ]: 3 : if (error != NULL)
120 : : {
121 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
122 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
123 : :
124 : 0 : return;
125 : : }
126 : :
127 [ + - ]: 3 : if (text == NULL)
128 : : return;
129 : :
130 [ - + ]: 3 : valent_clipboard_plugin_clipboard_connect (self, text, self->local_timestamp);
131 : : }
132 : :
133 : : static void
134 : 3 : valent_clipboard_write_text_cb (ValentClipboard *clipboard,
135 : : GAsyncResult *result,
136 : : gpointer user_data)
137 : : {
138 : 6 : g_autoptr (GError) error = NULL;
139 : :
140 [ + - ]: 3 : g_assert (VALENT_IS_CLIPBOARD (clipboard));
141 : :
142 [ - + - - ]: 3 : if (!valent_clipboard_write_text_finish (clipboard, result, &error) &&
143 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
144 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
145 : 3 : }
146 : :
147 : : static void
148 : 2 : on_auto_pull_changed (GSettings *settings,
149 : : const char *key,
150 : : ValentClipboardPlugin *self)
151 : : {
152 : 2 : ValentDevice *device;
153 : 2 : ValentDeviceState state;
154 : 4 : g_autoptr (GCancellable) destroy = NULL;
155 : :
156 [ + - + - : 2 : g_assert (G_IS_SETTINGS (settings));
- + - - ]
157 [ - + ]: 2 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
158 : :
159 : 2 : self->auto_pull = g_settings_get_boolean (settings, "auto-pull");
160 : :
161 [ + + ]: 2 : if (!self->auto_pull)
162 : : return;
163 : :
164 : 1 : device = valent_extension_get_object (VALENT_EXTENSION (self));
165 : 1 : state = valent_device_get_state (device);
166 : :
167 [ - + ]: 1 : if ((state & VALENT_DEVICE_STATE_CONNECTED) != 0 ||
168 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0)
169 : : return;
170 : :
171 : 0 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
172 [ # # ]: 0 : valent_clipboard_write_text (self->clipboard,
173 : 0 : self->remote_text,
174 : : destroy,
175 : : (GAsyncReadyCallback)valent_clipboard_write_text_cb,
176 : : NULL);
177 : : }
178 : :
179 : : static void
180 : 2 : on_auto_push_changed (GSettings *settings,
181 : : const char *key,
182 : : ValentClipboardPlugin *self)
183 : : {
184 : 2 : ValentDevice *device;
185 : 2 : ValentDeviceState state;
186 : 4 : g_autoptr (GCancellable) destroy = NULL;
187 : :
188 [ + - + - : 2 : g_assert (G_IS_SETTINGS (settings));
- + - - ]
189 [ - + ]: 2 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
190 : :
191 : 2 : self->auto_push = g_settings_get_boolean (settings, "auto-push");
192 : :
193 [ + + ]: 2 : if (!self->auto_push)
194 : : return;
195 : :
196 : 1 : device = valent_extension_get_object (VALENT_EXTENSION (self));
197 : 1 : state = valent_device_get_state (device);
198 : :
199 [ - + ]: 1 : if ((state & VALENT_DEVICE_STATE_CONNECTED) != 0 ||
200 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0)
201 : : return;
202 : :
203 : 0 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
204 [ # # ]: 0 : valent_clipboard_read_text (valent_clipboard_get_default (),
205 : : destroy,
206 : : (GAsyncReadyCallback)valent_clipboard_read_text_cb,
207 : : self);
208 : :
209 : : }
210 : :
211 : : static void
212 : 5 : on_clipboard_changed (ValentClipboard *clipboard,
213 : : ValentClipboardPlugin *self)
214 : : {
215 : 10 : g_autoptr (GCancellable) destroy = NULL;
216 : :
217 [ + - ]: 5 : g_assert (VALENT_IS_CLIPBOARD (clipboard));
218 [ - + ]: 5 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
219 : :
220 : 5 : self->local_timestamp = valent_clipboard_get_timestamp (clipboard);
221 : :
222 [ + + ]: 5 : if (!self->auto_push)
223 : : return;
224 : :
225 : 3 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
226 [ + - ]: 3 : valent_clipboard_read_text (clipboard,
227 : : destroy,
228 : : (GAsyncReadyCallback)valent_clipboard_read_text_cb,
229 : : self);
230 : : }
231 : :
232 : : /*
233 : : * Remote Clipboard
234 : : */
235 : : static void
236 : 2 : valent_clipboard_plugin_handle_clipboard (ValentClipboardPlugin *self,
237 : : JsonNode *packet)
238 : : {
239 : 2 : const char *content;
240 : 2 : g_autoptr (GCancellable) destroy = NULL;
241 : :
242 [ + - ]: 2 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
243 [ - + ]: 2 : g_assert (VALENT_IS_PACKET (packet));
244 : :
245 [ - + ]: 2 : if (!valent_packet_get_string (packet, "content", &content))
246 : : {
247 : 0 : g_debug ("%s(): expected \"content\" field holding a string",
248 : : G_STRFUNC);
249 : 0 : return;
250 : : }
251 : :
252 : : /* The remote clipboard content is cached, for manual control over syncing,
253 : : * because there is no packet type for requesting it on-demand. */
254 : 2 : g_set_str (&self->remote_text, content);
255 : 2 : self->remote_timestamp = valent_timestamp_ms ();
256 : :
257 [ + + ]: 2 : if (!self->auto_pull)
258 : : return;
259 : :
260 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
261 [ + - ]: 1 : valent_clipboard_write_text (self->clipboard,
262 : 1 : self->remote_text,
263 : : destroy,
264 : : (GAsyncReadyCallback)valent_clipboard_write_text_cb,
265 : : NULL);
266 : : }
267 : :
268 : : static void
269 : 2 : valent_clipboard_plugin_handle_clipboard_connect (ValentClipboardPlugin *self,
270 : : JsonNode *packet)
271 : : {
272 : 2 : int64_t timestamp;
273 : 2 : const char *content;
274 : 2 : g_autoptr (GCancellable) destroy = NULL;
275 : :
276 [ + - ]: 2 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
277 [ - + ]: 2 : g_assert (VALENT_IS_PACKET (packet));
278 : :
279 [ - + ]: 2 : if (!valent_packet_get_int (packet, "timestamp", ×tamp))
280 : : {
281 : 0 : g_debug ("%s(): expected \"timestamp\" field holding an integer",
282 : : G_STRFUNC);
283 : 0 : return;
284 : : }
285 : :
286 [ - + ]: 2 : if (!valent_packet_get_string (packet, "content", &content))
287 : : {
288 : 0 : g_debug ("%s(): expected \"content\" field holding a string",
289 : : G_STRFUNC);
290 : 0 : return;
291 : : }
292 : :
293 : : /* The remote clipboard content is cached, for manual control over syncing,
294 : : * because there is no packet type for requesting it on-demand. */
295 [ + + ]: 2 : g_clear_pointer (&self->remote_text, g_free);
296 [ - + ]: 2 : self->remote_text = g_strdup (content);
297 : 2 : self->remote_timestamp = timestamp;
298 : :
299 [ + + ]: 2 : if (self->remote_timestamp <= self->local_timestamp)
300 : : return;
301 : :
302 [ + - ]: 1 : if (!self->auto_pull)
303 : : return;
304 : :
305 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
306 [ + - ]: 1 : valent_clipboard_write_text (self->clipboard,
307 : 1 : self->remote_text,
308 : : destroy,
309 : : (GAsyncReadyCallback)valent_clipboard_write_text_cb,
310 : : NULL);
311 : : }
312 : :
313 : : /*
314 : : * GActions
315 : : */
316 : : static void
317 : 1 : clipboard_pull_action (GSimpleAction *action,
318 : : GVariant *parameter,
319 : : gpointer user_data)
320 : : {
321 : 1 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (user_data);
322 : 0 : g_autoptr (GCancellable) destroy = NULL;
323 : :
324 [ + - ]: 1 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
325 : :
326 [ + - - + ]: 1 : if (self->remote_text == NULL || *self->remote_text == '\0')
327 : : {
328 : 0 : g_debug ("%s(): remote clipboard empty", G_STRFUNC);
329 : 0 : return;
330 : : }
331 : :
332 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
333 [ + - ]: 1 : valent_clipboard_write_text (self->clipboard,
334 : 1 : self->remote_text,
335 : : destroy,
336 : : (GAsyncReadyCallback)valent_clipboard_write_text_cb,
337 : : NULL);
338 : : }
339 : :
340 : : static void
341 : 1 : clipboard_push_action (GSimpleAction *action,
342 : : GVariant *parameter,
343 : : gpointer user_data)
344 : : {
345 : 1 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (user_data);
346 : 2 : g_autoptr (GCancellable) destroy = NULL;
347 : :
348 [ + - ]: 1 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
349 : :
350 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
351 [ + - ]: 1 : valent_clipboard_read_text (valent_clipboard_get_default (),
352 : : destroy,
353 : : (GAsyncReadyCallback)valent_clipboard_read_text_cb,
354 : : self);
355 : 1 : }
356 : :
357 : : static const GActionEntry actions[] = {
358 : : {"pull", clipboard_pull_action, NULL, NULL, NULL},
359 : : {"push", clipboard_push_action, NULL, NULL, NULL}
360 : : };
361 : :
362 : : /*
363 : : * ValentDevicePlugin
364 : : */
365 : : static void
366 : 17 : valent_clipboard_plugin_update_state (ValentDevicePlugin *plugin,
367 : : ValentDeviceState state)
368 : : {
369 : 17 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (plugin);
370 : 17 : gboolean available;
371 : :
372 [ + - ]: 17 : g_assert (VALENT_IS_CLIPBOARD_PLUGIN (self));
373 : :
374 : 17 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
375 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
376 : :
377 : 17 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
378 : :
379 [ + + ]: 17 : if (available)
380 : : {
381 [ + - ]: 5 : if (self->changed_id == 0)
382 : 5 : self->changed_id = g_signal_connect (self->clipboard,
383 : : "changed",
384 : : G_CALLBACK (on_clipboard_changed),
385 : : self);
386 : :
387 [ + + ]: 5 : if (self->auto_push)
388 : : {
389 : 20 : g_autoptr (GCancellable) destroy = NULL;
390 : :
391 : 3 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
392 [ + - ]: 3 : valent_clipboard_read_text (self->clipboard,
393 : : destroy,
394 : : (GAsyncReadyCallback)valent_clipboard_read_text_connect_cb,
395 : : self);
396 : : }
397 : : }
398 : : else
399 : : {
400 [ + + ]: 12 : g_clear_signal_handler (&self->changed_id, self->clipboard);
401 : : }
402 : 17 : }
403 : :
404 : : static void
405 : 4 : valent_clipboard_plugin_handle_packet (ValentDevicePlugin *plugin,
406 : : const char *type,
407 : : JsonNode *packet)
408 : : {
409 : 4 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (plugin);
410 : :
411 [ + - ]: 4 : g_assert (VALENT_IS_DEVICE_PLUGIN (plugin));
412 [ - + ]: 4 : g_assert (type != NULL);
413 [ - + ]: 4 : g_assert (VALENT_IS_PACKET (packet));
414 : :
415 : : /* The remote clipboard content changed */
416 [ + + ]: 4 : if (g_str_equal (type, "kdeconnect.clipboard"))
417 : 2 : valent_clipboard_plugin_handle_clipboard (self, packet);
418 : :
419 [ + - ]: 2 : else if (g_str_equal (type, "kdeconnect.clipboard.connect"))
420 : 2 : valent_clipboard_plugin_handle_clipboard_connect (self, packet);
421 : :
422 : : else
423 : 0 : g_assert_not_reached ();
424 : 4 : }
425 : :
426 : : /*
427 : : * ValentObject
428 : : */
429 : : static void
430 : 12 : valent_clipboard_plugin_destroy (ValentObject *object)
431 : : {
432 : 12 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (object);
433 : :
434 [ - + ]: 12 : g_clear_signal_handler (&self->changed_id, self->clipboard);
435 : :
436 : 12 : VALENT_OBJECT_CLASS (valent_clipboard_plugin_parent_class)->destroy (object);
437 : 12 : }
438 : :
439 : : /*
440 : : * GObject
441 : : */
442 : : static void
443 : 6 : valent_clipboard_plugin_constructed (GObject *object)
444 : : {
445 : 6 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (object);
446 : 6 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
447 : 6 : GSettings *settings = NULL;
448 : :
449 : 6 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
450 : : actions,
451 : : G_N_ELEMENTS (actions),
452 : : plugin);
453 : :
454 : 6 : settings = valent_extension_get_settings (VALENT_EXTENSION (plugin));
455 : 6 : self->auto_pull = g_settings_get_boolean (settings, "auto-pull");
456 : 6 : g_signal_connect_object (settings,
457 : : "changed::auto-pull",
458 : : G_CALLBACK (on_auto_pull_changed),
459 : : self, 0);
460 : :
461 : 6 : self->auto_push = g_settings_get_boolean (settings, "auto-push");
462 : 6 : g_signal_connect_object (settings,
463 : : "changed::auto-push",
464 : : G_CALLBACK (on_auto_push_changed),
465 : : self, 0);
466 : :
467 : 6 : self->clipboard = valent_clipboard_get_default ();
468 : 6 : self->local_timestamp = valent_clipboard_get_timestamp (self->clipboard);
469 : :
470 : 6 : G_OBJECT_CLASS (valent_clipboard_plugin_parent_class)->constructed (object);
471 : 6 : }
472 : :
473 : : static void
474 : 6 : valent_clipboard_plugin_finalize (GObject *object)
475 : : {
476 : 6 : ValentClipboardPlugin *self = VALENT_CLIPBOARD_PLUGIN (object);
477 : :
478 [ + + ]: 6 : g_clear_pointer (&self->remote_text, g_free);
479 : :
480 : 6 : G_OBJECT_CLASS (valent_clipboard_plugin_parent_class)->finalize (object);
481 : 6 : }
482 : :
483 : : static void
484 : 18 : valent_clipboard_plugin_class_init (ValentClipboardPluginClass *klass)
485 : : {
486 : 18 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
487 : 18 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
488 : 18 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
489 : :
490 : 18 : object_class->constructed = valent_clipboard_plugin_constructed;
491 : 18 : object_class->finalize = valent_clipboard_plugin_finalize;
492 : :
493 : 18 : vobject_class->destroy = valent_clipboard_plugin_destroy;
494 : :
495 : 18 : plugin_class->handle_packet = valent_clipboard_plugin_handle_packet;
496 : 18 : plugin_class->update_state = valent_clipboard_plugin_update_state;
497 : : }
498 : :
499 : : static void
500 : 6 : valent_clipboard_plugin_init (ValentClipboardPlugin *self)
501 : : {
502 : 6 : }
503 : :
|