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-channel-service"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <json-glib/json-glib.h>
10 : : #include <libpeas.h>
11 : : #include <libvalent-core.h>
12 : :
13 : : #include "valent-certificate.h"
14 : : #include "valent-channel.h"
15 : : #include "valent-device.h"
16 : : #include "valent-device-common.h"
17 : : #include "valent-packet.h"
18 : :
19 : : #include "valent-channel-service.h"
20 : :
21 : : /**
22 : : * ValentChannelService:
23 : : *
24 : : * An abstract base class for connection backends.
25 : : *
26 : : * `ValentChannelService` is a base class for plugins that implement an interface
27 : : * to negotiate connections with other devices.
28 : : *
29 : : * ## Implementation Notes
30 : : *
31 : : * Implementations may safely invoke [method@Valent.ChannelService.channel] from
32 : : * any thread; it is guaranteed to be emitted in the main thread.
33 : : *
34 : : * ## `.plugin` File
35 : : *
36 : : * Channel services have no special fields in the `.plugin` file.
37 : : *
38 : : * Since: 1.0
39 : : */
40 : :
41 : : typedef struct
42 : : {
43 : : GTlsCertificate *certificate;
44 : : const char *id;
45 : : JsonNode *identity;
46 : : char *name;
47 : : GSettings *settings;
48 : : GHashTable *channels;
49 : : } ValentChannelServicePrivate;
50 : :
51 [ + + + - ]: 733 : G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ValentChannelService, valent_channel_service, VALENT_TYPE_EXTENSION);
52 : :
53 : : typedef enum {
54 : : PROP_CERTIFICATE = 1,
55 : : PROP_ID,
56 : : PROP_IDENTITY,
57 : : } ValentChannelServiceProperty;
58 : :
59 : : static GParamSpec *properties[PROP_IDENTITY + 1] = { NULL, };
60 : :
61 : : typedef enum {
62 : : CHANNEL,
63 : : } ValentChannelServiceSignal;
64 : :
65 : : static guint signals[CHANNEL + 1] = { 0, };
66 : :
67 : :
68 : : typedef struct
69 : : {
70 : : GRecMutex lock;
71 : : GWeakRef service;
72 : : ValentChannel *channel;
73 : : } ChannelEmission;
74 : :
75 : : static gboolean
76 : 0 : valent_channel_service_channel_main (gpointer data)
77 : : {
78 : 0 : ChannelEmission *emission = data;
79 : 0 : g_autoptr (ValentChannelService) service = NULL;
80 : :
81 [ # # ]: 0 : g_assert (VALENT_IS_MAIN_THREAD ());
82 : :
83 : 0 : g_rec_mutex_lock (&emission->lock);
84 [ # # ]: 0 : if ((service = g_weak_ref_get (&emission->service)) != NULL)
85 : 0 : valent_channel_service_channel (service, emission->channel);
86 : :
87 : 0 : g_weak_ref_clear (&emission->service);
88 [ # # ]: 0 : g_clear_object (&emission->channel);
89 : 0 : g_rec_mutex_unlock (&emission->lock);
90 : 0 : g_rec_mutex_clear (&emission->lock);
91 : 0 : g_clear_pointer (&emission, g_free);
92 : :
93 [ # # ]: 0 : return G_SOURCE_REMOVE;
94 : : }
95 : :
96 : : static void
97 : 1 : on_channel_destroyed (ValentChannelService *self,
98 : : ValentChannel *channel)
99 : : {
100 : 1 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
101 : 2 : g_autoptr (GTlsCertificate) certificate = NULL;
102 : 1 : const char *device_id = NULL;
103 : :
104 [ - + ]: 1 : g_assert (VALENT_IS_CHANNEL_SERVICE (self));
105 [ + - ]: 1 : g_assert (VALENT_IS_CHANNEL (channel));
106 : :
107 : 1 : certificate = valent_channel_ref_certificate (VALENT_CHANNEL (channel));
108 : 1 : device_id = valent_certificate_get_common_name (certificate);
109 [ + - ]: 1 : g_hash_table_remove (priv->channels, device_id);
110 : 1 : }
111 : :
112 : : static gboolean
113 : 8 : valent_channel_service_verify_channel (ValentChannelService *self,
114 : : ValentChannel *channel)
115 : : {
116 : 8 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
117 : 8 : ValentChannel *current = NULL;
118 : 16 : g_autoptr (GTlsCertificate) peer_certificate = NULL;
119 : 8 : const char *peer_certificate_cn = NULL;
120 : 8 : JsonNode *peer_identity = NULL;
121 : 8 : const char *device_id = NULL;
122 : 8 : const char *device_name = NULL;
123 : :
124 [ - + ]: 8 : g_assert (VALENT_IS_CHANNEL_SERVICE (self));
125 [ + - ]: 8 : g_assert (VALENT_IS_CHANNEL (channel));
126 : :
127 : : /* Ignore channels with an invalid deviceId or deviceName
128 : : */
129 : 8 : peer_identity = valent_channel_get_peer_identity (channel);
130 [ + - - + ]: 16 : if (!valent_packet_get_string (peer_identity, "deviceId", &device_id) ||
131 : 8 : !valent_device_validate_id (device_id))
132 : : {
133 : 0 : g_warning ("%s(): invalid device ID \"%s\"", G_STRFUNC, device_id);
134 : 0 : return FALSE;
135 : : }
136 : :
137 [ + - - + ]: 16 : if (!valent_packet_get_string (peer_identity, "deviceName", &device_name) ||
138 : 8 : !valent_device_validate_name (device_name))
139 : : {
140 : 0 : g_warning ("%s(): invalid device name \"%s\"", G_STRFUNC, device_name);
141 : 0 : return FALSE;
142 : : }
143 : :
144 : : /* Ensure the deviceId field matches the certificate common name
145 : : */
146 : 8 : peer_certificate = valent_channel_ref_peer_certificate (channel);
147 : 8 : peer_certificate_cn = valent_certificate_get_common_name (peer_certificate);
148 [ - + ]: 8 : if (g_strcmp0 (device_id, peer_certificate_cn) != 0)
149 : : {
150 : 0 : g_warning ("%s(): device ID does not match certificate common name",
151 : : G_STRFUNC);
152 : 0 : return FALSE;
153 : : }
154 : :
155 : : /* Ensure the certificate matches that of any existing channel
156 : : */
157 : 8 : current = g_hash_table_lookup (priv->channels, device_id);
158 [ - + ]: 8 : if (current != NULL)
159 : : {
160 [ - + ]: 8 : g_autoptr (GTlsCertificate) certificate = NULL;
161 : :
162 : 0 : certificate = valent_channel_ref_peer_certificate (VALENT_CHANNEL (current));
163 [ # # ]: 0 : if (!g_tls_certificate_is_same (certificate, peer_certificate))
164 : : {
165 : 0 : g_warning ("%s(): existing channel with different certificate",
166 : : G_STRFUNC);
167 [ # # ]: 0 : return FALSE;
168 : : }
169 : : }
170 : :
171 : : /* Track the channel until destruction
172 : : */
173 [ - + ]: 8 : g_hash_table_replace (priv->channels,
174 : 8 : g_strdup (device_id),
175 : : g_object_ref (channel));
176 : 8 : g_signal_connect_object (channel,
177 : : "destroy",
178 : : G_CALLBACK (on_channel_destroyed),
179 : : self,
180 : : G_CONNECT_SWAPPED);
181 : :
182 : 8 : return TRUE;
183 : : }
184 : :
185 : : /*
186 : : * Identity Packet Helpers
187 : : */
188 : : static const char *
189 : 23 : get_chassis_type (void)
190 : : {
191 : 23 : static size_t guard = 0;
192 : 23 : static char *chassis = NULL;
193 : :
194 [ + + + - ]: 23 : if (g_once_init_enter (&guard))
195 : : {
196 : 6 : g_autoptr (GDBusConnection) connection = NULL;
197 [ + + ]: 6 : g_autoptr (GVariant) reply = NULL;
198 [ - + ]: 6 : g_autofree char *str = NULL;
199 : 6 : uint64_t type;
200 : :
201 : 6 : connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
202 : :
203 [ + + ]: 6 : if (connection == NULL)
204 : 5 : VALENT_GOTO (dmi_fallback);
205 : :
206 : 1 : reply = g_dbus_connection_call_sync (connection,
207 : : "org.freedesktop.hostname1",
208 : : "/org/freedesktop/hostname1",
209 : : "org.freedesktop.DBus.Properties",
210 : : "Get",
211 : : g_variant_new ("(ss)",
212 : : "org.freedesktop.hostname1",
213 : : "Chassis"),
214 : : G_VARIANT_TYPE ("(v)"),
215 : : G_DBUS_CALL_FLAGS_NONE,
216 : : -1,
217 : : NULL,
218 : : NULL);
219 : :
220 [ - + ]: 1 : if (reply != NULL)
221 : : {
222 : 0 : g_autoptr (GVariant) value = NULL;
223 : :
224 : 0 : g_variant_get (reply, "(v)", &value);
225 : 0 : g_variant_get (value, "s", &chassis);
226 : :
227 : : /* NOTE: "phone" is the KDE Connect deviceType */
228 [ # # ]: 0 : if (g_str_equal (chassis, "handset"))
229 : 0 : g_set_str (&chassis, "phone");
230 : :
231 [ # # ]: 0 : VALENT_GOTO (leave);
232 : : }
233 : :
234 : : /* Fallback to DMI. See the SMBIOS Specification 3.0 section 7.4.1:
235 : : * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
236 : : */
237 : 1 : dmi_fallback:
238 [ + - + - ]: 12 : if (!g_file_get_contents ("/sys/class/dmi/id/chassis_type", &str, NULL, NULL) ||
239 : 6 : !g_ascii_string_to_unsigned (str, 10, 0, G_MAXUINT64, &type, NULL))
240 : 6 : type = 0x3;
241 : :
242 [ + - - - : 6 : switch (type)
- ]
243 : : {
244 : 6 : case 0x3: /* Desktop */
245 : : case 0x4: /* Low Profile Desktop */
246 : : case 0x6: /* Mini Tower */
247 : : case 0x7: /* Tower */
248 : 6 : g_set_str (&chassis, "desktop");
249 : 6 : break;
250 : :
251 : 0 : case 0x8: /* Portable */
252 : : case 0x9: /* Laptop */
253 : : case 0xA: /* Notebook */
254 : : case 0xE: /* Sub Notebook */
255 : 0 : g_set_str (&chassis, "laptop");
256 : 0 : break;
257 : :
258 : 0 : case 0xB: /* Hand Held */
259 : 0 : g_set_str (&chassis, "phone");
260 : 0 : break;
261 : :
262 : 0 : case 0x1E: /* Tablet */
263 : 0 : g_set_str (&chassis, "tablet");
264 : 0 : break;
265 : :
266 : 0 : default:
267 : 0 : g_set_str (&chassis, "desktop");
268 : : }
269 : :
270 : 6 : leave:
271 : 6 : g_once_init_leave (&guard, 1);
272 : : }
273 : :
274 : 23 : return chassis;
275 : : }
276 : :
277 : : /**
278 : : * collect_capabilities:
279 : : * @info: a `PeasPluginInfo`
280 : : * @incoming: a `GHashTable`
281 : : * @outgoing: a `GHashTable`
282 : : *
283 : : * Collect the capabilities from @info and add them to @incoming and @outgoing,
284 : : * using g_hash_table_add() to coalesce duplicates.
285 : : */
286 : : static inline void
287 : 125 : collect_capabilities (PeasPluginInfo *info,
288 : : GHashTable *incoming,
289 : : GHashTable *outgoing)
290 : : {
291 : 125 : const char *data = NULL;
292 : :
293 [ + + ]: 125 : if ((data = peas_plugin_info_get_external_data (info, "DevicePluginIncoming")) != NULL)
294 : : {
295 : 68 : g_autofree char **capabilities = NULL;
296 : :
297 : 68 : capabilities = g_strsplit (data, ";", -1);
298 : :
299 [ + + ]: 199 : for (unsigned int i = 0; capabilities[i] != NULL; i++)
300 : 131 : g_hash_table_add (incoming, g_steal_pointer (&capabilities[i]));
301 : : }
302 : :
303 [ + + ]: 125 : if ((data = peas_plugin_info_get_external_data (info, "DevicePluginOutgoing")) != NULL)
304 : : {
305 : 68 : g_autofree char **capabilities = NULL;
306 : :
307 : 68 : capabilities = g_strsplit (data, ";", -1);
308 : :
309 [ + + ]: 209 : for (unsigned int i = 0; capabilities[i] != NULL; i++)
310 : 141 : g_hash_table_add (outgoing, g_steal_pointer (&capabilities[i]));
311 : : }
312 : 125 : }
313 : :
314 : : static void
315 : 26 : on_device_name_changed (GSettings *settings,
316 : : const char *key_name,
317 : : ValentChannelService *self)
318 : : {
319 : 26 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
320 : 26 : g_autofree char *name = NULL;
321 : :
322 [ - + ]: 26 : g_return_if_fail (VALENT_IS_CHANNEL_SERVICE (self));
323 : :
324 : 26 : name = g_settings_get_string (settings, "name");
325 [ + - + + ]: 26 : if (name == NULL || *name == '\0')
326 : : {
327 : 6 : g_auto (GStrv) hostname = NULL;
328 : :
329 : 6 : hostname = g_strsplit (g_get_host_name (), ".", 2);
330 : 6 : g_settings_set_string (settings, "name", hostname[0]);
331 : 6 : return;
332 : : }
333 : :
334 [ + - ]: 20 : if (g_set_str (&priv->name, name))
335 : : {
336 : 20 : valent_object_lock (VALENT_OBJECT (self));
337 [ - + ]: 20 : if (priv->identity)
338 : : {
339 : 0 : JsonObject *body = valent_packet_get_body (priv->identity);
340 : 0 : json_object_set_string_member (body, "deviceName", priv->name);
341 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IDENTITY]);
342 : : }
343 : 20 : valent_object_unlock (VALENT_OBJECT (self));
344 : : }
345 : : }
346 : :
347 : : /* LCOV_EXCL_START */
348 : : static void
349 : : valent_channel_service_real_build_identity (ValentChannelService *service)
350 : : {
351 : : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (service);
352 : : PeasEngine *engine;
353 : : g_autoptr (JsonBuilder) builder = NULL;
354 : : g_autoptr (GHashTable) incoming = NULL;
355 : : g_autoptr (GHashTable) outgoing = NULL;
356 : : GHashTableIter in_iter, out_iter;
357 : : const char *capability = NULL;
358 : : unsigned int n_plugins = 0;
359 : :
360 : : g_assert (VALENT_IS_CHANNEL_SERVICE (service));
361 : :
362 : : /* Filter the supported plugins and collect their capabilities */
363 : : incoming = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
364 : : outgoing = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
365 : :
366 : : engine = valent_get_plugin_engine ();
367 : : n_plugins = g_list_model_get_n_items (G_LIST_MODEL (engine));
368 : :
369 : : for (unsigned int i = 0; i < n_plugins; i++)
370 : : {
371 : : g_autoptr (PeasPluginInfo) info = NULL;
372 : :
373 : : info = g_list_model_get_item (G_LIST_MODEL (engine), i);
374 : : collect_capabilities (info, incoming, outgoing);
375 : : }
376 : :
377 : : /* Build the identity packet */
378 : : builder = json_builder_new ();
379 : : json_builder_begin_object (builder);
380 : :
381 : : /* Packet */
382 : : json_builder_set_member_name (builder, "id");
383 : : json_builder_add_int_value (builder, 0);
384 : : json_builder_set_member_name (builder, "type");
385 : : json_builder_add_string_value (builder, "kdeconnect.identity");
386 : :
387 : : /* Body */
388 : : json_builder_set_member_name (builder, "body");
389 : : json_builder_begin_object (builder);
390 : :
391 : : /* Metadata */
392 : : json_builder_set_member_name (builder, "deviceId");
393 : : json_builder_add_string_value (builder, priv->id);
394 : : json_builder_set_member_name (builder, "deviceName");
395 : : json_builder_add_string_value (builder, priv->name);
396 : : json_builder_set_member_name (builder, "deviceType");
397 : : json_builder_add_string_value (builder, get_chassis_type());
398 : : json_builder_set_member_name (builder, "protocolVersion");
399 : : json_builder_add_int_value (builder, VALENT_NETWORK_PROTOCOL_MAX);
400 : :
401 : : /* Incoming Capabilities */
402 : : json_builder_set_member_name (builder, "incomingCapabilities");
403 : : json_builder_begin_array (builder);
404 : :
405 : : g_hash_table_iter_init (&in_iter, incoming);
406 : :
407 : : while (g_hash_table_iter_next (&in_iter, (void **)&capability, NULL))
408 : : json_builder_add_string_value (builder, capability);
409 : :
410 : : json_builder_end_array (builder);
411 : :
412 : : /* Outgoing Capabilities */
413 : : json_builder_set_member_name (builder, "outgoingCapabilities");
414 : : json_builder_begin_array (builder);
415 : :
416 : : g_hash_table_iter_init (&out_iter, outgoing);
417 : :
418 : : while (g_hash_table_iter_next (&out_iter, (void **)&capability, NULL))
419 : : json_builder_add_string_value (builder, capability);
420 : :
421 : : json_builder_end_array (builder);
422 : :
423 : : /* End Body, Packet */
424 : : json_builder_end_object (builder);
425 : : json_builder_end_object (builder);
426 : :
427 : :
428 : : /* Store the identity */
429 : : valent_object_lock (VALENT_OBJECT (service));
430 : : g_clear_pointer (&priv->identity, json_node_unref);
431 : : priv->identity = json_builder_get_root (builder);
432 : : valent_object_unlock (VALENT_OBJECT (service));
433 : : }
434 : :
435 : : static void
436 : : valent_channel_service_real_channel (ValentChannelService *service,
437 : : ValentChannel *channel)
438 : : {
439 : : g_assert (VALENT_IS_CHANNEL_SERVICE (service));
440 : : g_assert (VALENT_IS_CHANNEL (channel));
441 : : }
442 : :
443 : : static void
444 : : valent_channel_service_real_identify (ValentChannelService *service,
445 : : const char *target)
446 : : {
447 : : g_assert (VALENT_IS_CHANNEL_SERVICE (service));
448 : : }
449 : : /* LCOV_EXCL_STOP */
450 : :
451 : : /*
452 : : * GObject
453 : : */
454 : : static void
455 : 20 : valent_channel_service_constructed (GObject *object)
456 : : {
457 : 20 : ValentChannelService *self = VALENT_CHANNEL_SERVICE (object);
458 : 20 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
459 : :
460 : 20 : G_OBJECT_CLASS (valent_channel_service_parent_class)->constructed (object);
461 : :
462 : 20 : valent_object_lock (VALENT_OBJECT (self));
463 [ + - ]: 20 : if (priv->certificate == NULL)
464 : : {
465 : 40 : g_autoptr (ValentContext) context = NULL;
466 [ + - ]: 20 : g_autoptr (GFile) config = NULL;
467 : :
468 : 20 : context = valent_context_new (NULL, NULL, NULL);
469 : 20 : config = valent_context_get_config_file (context, ".");
470 [ + - ]: 20 : priv->certificate = valent_certificate_new_sync (g_file_peek_path (config),
471 : : NULL);
472 : : }
473 : :
474 : 20 : priv->id = valent_certificate_get_common_name (priv->certificate);
475 : 20 : priv->settings = g_settings_new ("ca.andyholmes.Valent");
476 : 20 : g_signal_connect_object (priv->settings,
477 : : "changed::name",
478 : : G_CALLBACK (on_device_name_changed),
479 : : self,
480 : : G_CONNECT_DEFAULT);
481 : 20 : on_device_name_changed (priv->settings, NULL, self);
482 : 20 : valent_object_unlock (VALENT_OBJECT (self));
483 : :
484 : 20 : valent_channel_service_build_identity (self);
485 : 20 : }
486 : :
487 : : static void
488 : 20 : valent_channel_service_finalize (GObject *object)
489 : : {
490 : 20 : ValentChannelService *self = VALENT_CHANNEL_SERVICE (object);
491 : 20 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
492 : :
493 : 20 : valent_object_lock (VALENT_OBJECT (self));
494 [ + - ]: 20 : g_clear_object (&priv->certificate);
495 [ + - ]: 20 : g_clear_pointer (&priv->identity, json_node_unref);
496 [ + - ]: 20 : g_clear_pointer (&priv->name, g_free);
497 [ + - ]: 20 : g_clear_object (&priv->settings);
498 [ + - ]: 20 : g_clear_pointer (&priv->channels, g_hash_table_unref);
499 : 20 : valent_object_unlock (VALENT_OBJECT (self));
500 : :
501 : 20 : G_OBJECT_CLASS (valent_channel_service_parent_class)->finalize (object);
502 : 20 : }
503 : :
504 : : static void
505 : 6 : valent_channel_service_get_property (GObject *object,
506 : : guint prop_id,
507 : : GValue *value,
508 : : GParamSpec *pspec)
509 : : {
510 : 6 : ValentChannelService *self = VALENT_CHANNEL_SERVICE (object);
511 : 6 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
512 : :
513 [ + + + - ]: 6 : switch ((ValentChannelServiceProperty)prop_id)
514 : : {
515 : : case PROP_CERTIFICATE:
516 : 1 : valent_object_lock (VALENT_OBJECT (self));
517 : 1 : g_value_set_object (value, priv->certificate);
518 : 1 : valent_object_unlock (VALENT_OBJECT (self));
519 : 1 : break;
520 : :
521 : : case PROP_ID:
522 : 1 : valent_object_lock (VALENT_OBJECT (self));
523 : 1 : g_value_set_string (value, priv->id);
524 : 1 : valent_object_unlock (VALENT_OBJECT (self));
525 : 1 : break;
526 : :
527 : : case PROP_IDENTITY:
528 : 4 : valent_object_lock (VALENT_OBJECT (self));
529 : 4 : g_value_set_boxed (value, priv->identity);
530 : 4 : valent_object_unlock (VALENT_OBJECT (self));
531 : 4 : break;
532 : :
533 : 0 : default:
534 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
535 : : }
536 : 6 : }
537 : :
538 : : static void
539 : 20 : valent_channel_service_set_property (GObject *object,
540 : : guint prop_id,
541 : : const GValue *value,
542 : : GParamSpec *pspec)
543 : : {
544 : 20 : ValentChannelService *self = VALENT_CHANNEL_SERVICE (object);
545 : 20 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
546 : :
547 [ + - ]: 20 : switch ((ValentChannelServiceProperty)prop_id)
548 : : {
549 : : case PROP_CERTIFICATE:
550 : 20 : valent_object_lock (VALENT_OBJECT (self));
551 : 20 : priv->certificate = g_value_dup_object (value);
552 : 20 : valent_object_unlock (VALENT_OBJECT (self));
553 : 20 : break;
554 : :
555 : 0 : case PROP_ID:
556 : : case PROP_IDENTITY:
557 : : default:
558 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
559 : : }
560 : 20 : }
561 : :
562 : : static void
563 : 51 : valent_channel_service_class_init (ValentChannelServiceClass *klass)
564 : : {
565 : 51 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
566 : 51 : ValentChannelServiceClass *service_class = VALENT_CHANNEL_SERVICE_CLASS (klass);
567 : :
568 : 51 : object_class->constructed = valent_channel_service_constructed;
569 : 51 : object_class->finalize = valent_channel_service_finalize;
570 : 51 : object_class->get_property = valent_channel_service_get_property;
571 : 51 : object_class->set_property = valent_channel_service_set_property;
572 : :
573 : 51 : service_class->build_identity = valent_channel_service_real_build_identity;
574 : 51 : service_class->channel = valent_channel_service_real_channel;
575 : 51 : service_class->identify = valent_channel_service_real_identify;
576 : :
577 : : /**
578 : : * ValentChannelService:certificate: (getter ref_certificate)
579 : : *
580 : : * The TLS certificate the service uses to authenticate with other devices.
581 : : */
582 : 102 : properties [PROP_CERTIFICATE] =
583 : 51 : g_param_spec_object ("certificate", NULL, NULL,
584 : : G_TYPE_TLS_CERTIFICATE,
585 : : (G_PARAM_READWRITE |
586 : : G_PARAM_CONSTRUCT_ONLY |
587 : : G_PARAM_EXPLICIT_NOTIFY |
588 : : G_PARAM_STATIC_STRINGS));
589 : :
590 : : /**
591 : : * ValentChannelService:id: (getter dup_id)
592 : : *
593 : : * The local ID.
594 : : *
595 : : * This is the ID used to identify the local device, which should be unique
596 : : * among devices in a given network.
597 : : *
598 : : * This property is thread-safe. Emissions of [signal@GObject.Object::notify]
599 : : * are guaranteed to happen in the main thread.
600 : : *
601 : : * Since: 1.0
602 : : */
603 : 102 : properties [PROP_ID] =
604 : 51 : g_param_spec_string ("id", NULL, NULL,
605 : : NULL,
606 : : (G_PARAM_READABLE |
607 : : G_PARAM_EXPLICIT_NOTIFY |
608 : : G_PARAM_STATIC_STRINGS));
609 : :
610 : : /**
611 : : * ValentChannelService:identity: (getter ref_identity)
612 : : *
613 : : * The local identity packet.
614 : : *
615 : : * This is the identity packet sent by the [class@Valent.ChannelService]
616 : : * implementation to describe the local device.
617 : : *
618 : : * This property is thread-safe. Emissions of [signal@GObject.Object::notify]
619 : : * are guaranteed to happen in the main thread.
620 : : *
621 : : * Since: 1.0
622 : : */
623 : 102 : properties [PROP_IDENTITY] =
624 : 51 : g_param_spec_boxed ("identity", NULL, NULL,
625 : : JSON_TYPE_NODE,
626 : : (G_PARAM_READABLE |
627 : : G_PARAM_EXPLICIT_NOTIFY |
628 : : G_PARAM_STATIC_STRINGS));
629 : :
630 : 51 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
631 : :
632 : : /**
633 : : * ValentChannelService::channel:
634 : : * @service: a `ValentChannelService`
635 : : * @channel: a `ValentChannel`
636 : : *
637 : : * Emitted when a new channel has been negotiated.
638 : : *
639 : : * In practice, when this is emitted a [class@Valent.DeviceManager] will
640 : : * ensure a [class@Valent.Device] exists to take ownership of @channel.
641 : : *
642 : : * Since: 1.0
643 : : */
644 : 102 : signals [CHANNEL] =
645 : 51 : g_signal_new ("channel",
646 : : G_TYPE_FROM_CLASS (klass),
647 : : G_SIGNAL_RUN_LAST,
648 : : G_STRUCT_OFFSET (ValentChannelServiceClass, channel),
649 : : NULL, NULL,
650 : : g_cclosure_marshal_VOID__OBJECT,
651 : : G_TYPE_NONE, 1, VALENT_TYPE_CHANNEL);
652 : 51 : g_signal_set_va_marshaller (signals [CHANNEL],
653 : : G_TYPE_FROM_CLASS (klass),
654 : : g_cclosure_marshal_VOID__OBJECTv);
655 : 51 : }
656 : :
657 : : static void
658 : 20 : valent_channel_service_init (ValentChannelService *self)
659 : : {
660 : 20 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (self);
661 : :
662 : 20 : priv->channels = g_hash_table_new_full (g_str_hash,
663 : : g_str_equal,
664 : : g_free,
665 : : g_object_unref);
666 : 20 : }
667 : :
668 : : /**
669 : : * valent_channel_service_ref_certificate: (get-property certificate)
670 : : * @self: a `ValentChannelService`
671 : : *
672 : : * Get the TLS certificate for the service.
673 : : *
674 : : * Returns: (transfer full) (not nullable): the service TLS certificate
675 : : */
676 : : GTlsCertificate *
677 : 8 : valent_channel_service_ref_certificate (ValentChannelService *service)
678 : : {
679 : 8 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (service);
680 : 8 : GTlsCertificate *ret = NULL;
681 : :
682 [ - + ]: 8 : g_return_val_if_fail (VALENT_IS_CHANNEL_SERVICE (service), NULL);
683 : :
684 : 8 : valent_object_lock (VALENT_OBJECT (service));
685 : 8 : ret = g_object_ref (priv->certificate);
686 : 8 : valent_object_unlock (VALENT_OBJECT (service));
687 : :
688 : 8 : return g_steal_pointer (&ret);
689 : : }
690 : :
691 : : /**
692 : : * valent_channel_service_dup_id: (get-property id)
693 : : * @service: a `ValentChannelService`
694 : : *
695 : : * Get the local ID.
696 : : *
697 : : * Returns: (transfer full) (not nullable): the service ID
698 : : *
699 : : * Since: 1.0
700 : : */
701 : : char *
702 : 7 : valent_channel_service_dup_id (ValentChannelService *service)
703 : : {
704 : 7 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (service);
705 : 7 : char *ret;
706 : :
707 [ - + ]: 7 : g_return_val_if_fail (VALENT_IS_CHANNEL_SERVICE (service), NULL);
708 : :
709 : 7 : valent_object_lock (VALENT_OBJECT (service));
710 [ - + ]: 7 : ret = g_strdup (priv->id);
711 : 7 : valent_object_unlock (VALENT_OBJECT (service));
712 : :
713 : 7 : return g_steal_pointer (&ret);
714 : : }
715 : :
716 : : /**
717 : : * valent_channel_service_ref_identity: (get-property identity)
718 : : * @service: a `ValentChannelService`
719 : : *
720 : : * Get the local identity packet.
721 : : *
722 : : * Returns: (transfer full): a KDE Connect packet
723 : : *
724 : : * Since: 1.0
725 : : */
726 : : JsonNode *
727 : 22 : valent_channel_service_ref_identity (ValentChannelService *service)
728 : : {
729 : 22 : ValentChannelServicePrivate *priv = valent_channel_service_get_instance_private (service);
730 : 22 : JsonNode *ret;
731 : :
732 [ - + ]: 22 : g_return_val_if_fail (VALENT_IS_CHANNEL_SERVICE (service), NULL);
733 : :
734 : 22 : valent_object_lock (VALENT_OBJECT (service));
735 : 22 : ret = json_node_ref (priv->identity);
736 : 22 : valent_object_unlock (VALENT_OBJECT (service));
737 : :
738 : 22 : return g_steal_pointer (&ret);
739 : : }
740 : :
741 : : /**
742 : : * valent_channel_service_build_identity: (virtual build_identity)
743 : : * @service: a `ValentChannelService`
744 : : *
745 : : * Rebuild the local KDE Connect identity packet.
746 : : *
747 : : * This method is called to rebuild the identity packet used to identify the
748 : : * host device to remote devices.
749 : : *
750 : : * Implementations that override [vfunc@Valent.ChannelService.build_identity]
751 : : * should chain-up first, then call [method@Valent.ChannelService.ref_identity]
752 : : * and modify that.
753 : : *
754 : : * Since: 1.0
755 : : */
756 : : void
757 : 23 : valent_channel_service_build_identity (ValentChannelService *service)
758 : : {
759 : 23 : VALENT_ENTRY;
760 : :
761 [ - + ]: 23 : g_return_if_fail (VALENT_IS_CHANNEL_SERVICE (service));
762 : :
763 : 23 : valent_object_lock (VALENT_OBJECT (service));
764 : 23 : VALENT_CHANNEL_SERVICE_GET_CLASS (service)->build_identity (service);
765 : 23 : valent_object_unlock (VALENT_OBJECT (service));
766 : :
767 : 23 : VALENT_EXIT;
768 : : }
769 : :
770 : : /**
771 : : * valent_channel_service_identify: (virtual identify)
772 : : * @service: a `ValentChannelService`
773 : : * @target: (nullable): a target string
774 : : *
775 : : * Identify the host device to the network.
776 : : *
777 : : * This method is called to announce the availability of the host device to
778 : : * other devices.
779 : : *
780 : : * Implementations that override [vfunc@Valent.ChannelService.identify] may
781 : : * ignore @target or use it to address a particular device.
782 : : *
783 : : * Since: 1.0
784 : : */
785 : : void
786 : 6 : valent_channel_service_identify (ValentChannelService *service,
787 : : const char *target)
788 : : {
789 : 6 : VALENT_ENTRY;
790 : :
791 [ - + ]: 6 : g_return_if_fail (VALENT_IS_CHANNEL_SERVICE (service));
792 : :
793 : 6 : VALENT_CHANNEL_SERVICE_GET_CLASS (service)->identify (service, target);
794 : :
795 : 6 : VALENT_EXIT;
796 : : }
797 : :
798 : : /**
799 : : * valent_channel_service_channel:
800 : : * @service: a `ValentChannelService`
801 : : * @channel: a `ValentChannel`
802 : : *
803 : : * Emit [signal@Valent.ChannelService::channel] on @service.
804 : : *
805 : : * This method should only be called by implementations of
806 : : * [class@Valent.ChannelService].
807 : : *
808 : : * Since: 1.0
809 : : */
810 : : void
811 : 8 : valent_channel_service_channel (ValentChannelService *service,
812 : : ValentChannel *channel)
813 : : {
814 : 8 : ChannelEmission *emission;
815 : :
816 [ - + ]: 8 : g_return_if_fail (VALENT_IS_CHANNEL_SERVICE (service));
817 [ + - ]: 8 : g_return_if_fail (VALENT_IS_CHANNEL (channel));
818 : :
819 [ + - ]: 8 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
820 : : {
821 [ + - ]: 8 : if (valent_channel_service_verify_channel (service, channel))
822 : 8 : g_signal_emit (G_OBJECT (service), signals [CHANNEL], 0, channel);
823 [ # # ]: 0 : else if (!valent_object_in_destruction (VALENT_OBJECT (channel)))
824 : 0 : valent_object_destroy (VALENT_OBJECT (channel));
825 : :
826 : 8 : return;
827 : : }
828 : :
829 : 0 : emission = g_new0 (ChannelEmission, 1);
830 : 0 : g_rec_mutex_init (&emission->lock);
831 : 0 : g_rec_mutex_lock (&emission->lock);
832 : 0 : g_weak_ref_init (&emission->service, service);
833 : 0 : emission->channel = g_object_ref (channel);
834 : 0 : g_rec_mutex_unlock (&emission->lock);
835 : :
836 : 0 : g_timeout_add (0, valent_channel_service_channel_main, emission);
837 : : }
838 : :
|