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-telephony"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <math.h>
9 : :
10 : : #include <gio/gio.h>
11 : : #include <json-glib/json-glib.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-telephony.h"
15 : :
16 : :
17 : : /**
18 : : * ValentTelephony:
19 : : *
20 : : * A class for controlling pointer and keyboard devices.
21 : : *
22 : : * `ValentTelephony` is an abstraction of telephony support, intended for use by
23 : : * [class@Valent.DevicePlugin] implementations.
24 : : */
25 : :
26 : : struct _ValentTelephony
27 : : {
28 : : ValentObject parent_instance;
29 : :
30 : : GDBusObjectManager *manager;
31 : : GDBusProxy *modem;
32 : : GHashTable *modems;
33 : : };
34 : :
35 [ + + + - ]: 22 : G_DEFINE_FINAL_TYPE (ValentTelephony, valent_telephony, VALENT_TYPE_OBJECT)
36 : :
37 : : enum {
38 : : CHANGED,
39 : : N_SIGNALS
40 : : };
41 : :
42 : : static guint signals[N_SIGNALS] = { 0, };
43 : :
44 : :
45 : : static ValentTelephony *default_telephony = NULL;
46 : :
47 : : /*
48 : : * ModemManager-enums.h
49 : : */
50 : : typedef enum { /*< underscore_name=mm_modem_access_technology >*/
51 : : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN = 0,
52 : : MM_MODEM_ACCESS_TECHNOLOGY_POTS = 1 << 0,
53 : : MM_MODEM_ACCESS_TECHNOLOGY_GSM = 1 << 1,
54 : : MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT = 1 << 2,
55 : : MM_MODEM_ACCESS_TECHNOLOGY_GPRS = 1 << 3,
56 : : MM_MODEM_ACCESS_TECHNOLOGY_EDGE = 1 << 4,
57 : : MM_MODEM_ACCESS_TECHNOLOGY_UMTS = 1 << 5,
58 : : MM_MODEM_ACCESS_TECHNOLOGY_HSDPA = 1 << 6,
59 : : MM_MODEM_ACCESS_TECHNOLOGY_HSUPA = 1 << 7,
60 : : MM_MODEM_ACCESS_TECHNOLOGY_HSPA = 1 << 8,
61 : : MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS = 1 << 9,
62 : : MM_MODEM_ACCESS_TECHNOLOGY_1XRTT = 1 << 10,
63 : : MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 = 1 << 11,
64 : : MM_MODEM_ACCESS_TECHNOLOGY_EVDOA = 1 << 12,
65 : : MM_MODEM_ACCESS_TECHNOLOGY_EVDOB = 1 << 13,
66 : : MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14,
67 : : MM_MODEM_ACCESS_TECHNOLOGY_5GNR = 1 << 15,
68 : : MM_MODEM_ACCESS_TECHNOLOGY_ANY = 0xFFFFFFFF,
69 : : } ValentModemAccessTechnology;
70 : :
71 : : typedef enum { /*< underscore_name=mm_modem_state >*/
72 : : MM_MODEM_STATE_FAILED = -1,
73 : : MM_MODEM_STATE_UNKNOWN = 0,
74 : : MM_MODEM_STATE_INITIALIZING = 1,
75 : : MM_MODEM_STATE_LOCKED = 2,
76 : : MM_MODEM_STATE_DISABLED = 3,
77 : : MM_MODEM_STATE_DISABLING = 4,
78 : : MM_MODEM_STATE_ENABLING = 5,
79 : : MM_MODEM_STATE_ENABLED = 6,
80 : : MM_MODEM_STATE_SEARCHING = 7,
81 : : MM_MODEM_STATE_REGISTERED = 8,
82 : : MM_MODEM_STATE_DISCONNECTING = 9,
83 : : MM_MODEM_STATE_CONNECTING = 10,
84 : : MM_MODEM_STATE_CONNECTED = 11,
85 : : } ValentModemState;
86 : :
87 : :
88 : : static inline const char *
89 : 6 : get_telephony_type_string (unsigned int flags)
90 : : {
91 [ - - - - : 6 : switch (flags)
- - + -
+ ]
92 : : {
93 : : case MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN:
94 : : case MM_MODEM_ACCESS_TECHNOLOGY_POTS:
95 : : return "Unknown";
96 : :
97 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_GSM:
98 : : case MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT:
99 : 0 : return "GSM";
100 : :
101 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_GPRS:
102 : 0 : return "GPRS";
103 : :
104 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_EDGE:
105 : 0 : return "EDGE";
106 : :
107 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_UMTS:
108 : 0 : return "UMTS";
109 : :
110 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_HSDPA:
111 : : case MM_MODEM_ACCESS_TECHNOLOGY_HSUPA:
112 : : case MM_MODEM_ACCESS_TECHNOLOGY_HSUPA | MM_MODEM_ACCESS_TECHNOLOGY_HSDPA:
113 : : case MM_MODEM_ACCESS_TECHNOLOGY_HSPA:
114 : : case MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS:
115 : 0 : return "HSPA";
116 : :
117 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_1XRTT:
118 : : case MM_MODEM_ACCESS_TECHNOLOGY_EVDO0:
119 : : case MM_MODEM_ACCESS_TECHNOLOGY_EVDOA:
120 : : case MM_MODEM_ACCESS_TECHNOLOGY_EVDOB:
121 : 0 : return "CDMA2000";
122 : :
123 : 2 : case MM_MODEM_ACCESS_TECHNOLOGY_LTE:
124 : 2 : return "LTE";
125 : :
126 : 0 : case MM_MODEM_ACCESS_TECHNOLOGY_5GNR:
127 : 0 : return "5G";
128 : :
129 : : case MM_MODEM_ACCESS_TECHNOLOGY_ANY:
130 : : return "Unknown";
131 : :
132 : : default:
133 : : return "Unknown";
134 : : }
135 : : }
136 : :
137 : : static void
138 : 4 : on_properties_changed (GDBusProxy *proxy,
139 : : GVariant *changed_properties,
140 : : GStrv invalidated,
141 : : ValentTelephony *self)
142 : : {
143 : 4 : GVariantIter iter;
144 : 4 : const char *property;
145 : 4 : GVariant *value;
146 : 4 : gboolean changed = FALSE;
147 : :
148 : 4 : g_variant_iter_init (&iter, changed_properties);
149 : :
150 [ + + ]: 44 : while (g_variant_iter_loop (&iter, "{sv}", &property, &value))
151 : : {
152 [ + + ]: 40 : if (g_str_equal (property, "AccessTechnologies"))
153 : : changed = TRUE;
154 [ + + ]: 36 : else if (g_str_equal (property, "SignalQuality"))
155 : : changed = TRUE;
156 [ + + ]: 32 : else if (g_str_equal (property, "State"))
157 : 12 : changed = TRUE;
158 : : }
159 : :
160 [ + - ]: 4 : if (changed)
161 : 4 : g_signal_emit (G_OBJECT (self), signals [CHANGED], 0);
162 : 4 : }
163 : :
164 : : static void
165 : 2 : g_dbus_proxy_new_for_bus_cb (GObject *object,
166 : : GAsyncResult *result,
167 : : gpointer user_data)
168 : : {
169 : 2 : g_autoptr (ValentTelephony) self = VALENT_TELEPHONY (user_data);
170 [ - - ]: 2 : g_autoptr (GDBusProxy) proxy = NULL;
171 [ - - ]: 2 : g_autoptr (GError) error = NULL;
172 : 2 : const char *object_path;
173 : :
174 : 2 : proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
175 : :
176 [ - + ]: 2 : if (proxy == NULL)
177 : : {
178 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
179 [ # # ]: 0 : return;
180 : : }
181 : :
182 : 2 : g_signal_connect_object (proxy,
183 : : "g-properties-changed",
184 : : G_CALLBACK (on_properties_changed),
185 : : self,
186 : : G_CONNECT_DEFAULT);
187 : :
188 : 2 : object_path = g_dbus_proxy_get_object_path (proxy);
189 [ - + ]: 2 : g_hash_table_replace (self->modems,
190 : 2 : g_strdup (object_path),
191 : : g_steal_pointer (&proxy));
192 : :
193 [ - + ]: 2 : g_signal_emit (G_OBJECT (self), signals [CHANGED], 0);
194 : : }
195 : :
196 : : static void
197 : 2 : on_modem_added (GDBusObjectManager *manager,
198 : : GDBusObject *object,
199 : : ValentTelephony *self)
200 : : {
201 : 2 : const char *object_path;
202 : :
203 [ + - + - : 2 : g_assert (G_IS_DBUS_OBJECT_MANAGER (manager));
+ - - + ]
204 [ + - + - : 2 : g_assert (G_IS_DBUS_OBJECT (object));
+ - + - ]
205 [ + - ]: 2 : g_assert (VALENT_IS_TELEPHONY (self));
206 : :
207 : 2 : object_path = g_dbus_object_get_object_path (object);
208 : :
209 [ - + + - : 2 : if (!g_str_has_prefix (object_path, "/org/freedesktop/ModemManager1/Modem"))
- + ]
210 : 0 : return;
211 : :
212 : 2 : g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
213 : : G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
214 : : NULL,
215 : : "org.freedesktop.ModemManager1",
216 : : object_path,
217 : : "org.freedesktop.ModemManager1.Modem",
218 : : NULL,
219 : : (GAsyncReadyCallback)g_dbus_proxy_new_for_bus_cb,
220 : : g_object_ref (self));
221 : : }
222 : :
223 : : static void
224 : 2 : on_modem_removed (GDBusObjectManager *manager,
225 : : GDBusObject *object,
226 : : ValentTelephony *self)
227 : : {
228 : 2 : const char *object_path;
229 : :
230 [ + - + - : 2 : g_assert (G_IS_DBUS_OBJECT_MANAGER (manager));
+ - - + ]
231 [ + - + - : 2 : g_assert (G_IS_DBUS_OBJECT (object));
+ - + - ]
232 [ + - ]: 2 : g_assert (VALENT_IS_TELEPHONY (self));
233 : :
234 : 2 : object_path = g_dbus_object_get_object_path (object);
235 : :
236 [ - + + - : 2 : if (!g_str_has_prefix (object_path, "/org/freedesktop/ModemManager1/Modem"))
- + ]
237 : 0 : return;
238 : :
239 [ + - ]: 2 : if (g_hash_table_remove (self->modems, object_path))
240 : 2 : g_signal_emit (G_OBJECT (self), signals [CHANGED], 0);
241 : : }
242 : :
243 : : static void
244 : 2 : g_dbus_object_manager_client_new_for_bus_cb (GObject *object,
245 : : GAsyncResult *result,
246 : : gpointer user_data)
247 : : {
248 : 2 : g_autoptr (ValentTelephony) self = VALENT_TELEPHONY (user_data);
249 : 2 : g_autolist (GDBusObject) modems = NULL;
250 : 2 : g_autoptr (GError) error = NULL;
251 : :
252 : 2 : self->manager = g_dbus_object_manager_client_new_for_bus_finish (result,
253 : : &error);
254 : :
255 [ - + ]: 2 : if (self->manager == NULL)
256 : : {
257 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
258 [ # # ]: 0 : return;
259 : : }
260 : :
261 : 2 : modems = g_dbus_object_manager_get_objects (self->manager);
262 : :
263 [ + + ]: 3 : for (const GList *iter = modems; iter; iter = iter->next)
264 : 1 : on_modem_added (self->manager, iter->data, self);
265 : :
266 : 2 : g_signal_connect_object (self->manager,
267 : : "object-added",
268 : : G_CALLBACK (on_modem_added),
269 : : self,
270 : : G_CONNECT_DEFAULT);
271 [ - + ]: 2 : g_signal_connect_object (self->manager,
272 : : "object-removed",
273 : : G_CALLBACK (on_modem_removed),
274 : : self,
275 : : G_CONNECT_DEFAULT);
276 : : }
277 : :
278 : : /*
279 : : * ValentObject
280 : : */
281 : : static void
282 : 1 : valent_telephony_destroy (ValentObject *object)
283 : : {
284 : 1 : ValentTelephony *self = VALENT_TELEPHONY (object);
285 : :
286 : 1 : g_signal_handlers_disconnect_by_data (self->manager, self);
287 [ + - ]: 1 : g_clear_object (&self->manager);
288 : :
289 : 1 : VALENT_OBJECT_CLASS (valent_telephony_parent_class)->destroy (object);
290 : 1 : }
291 : :
292 : : /*
293 : : * GObject
294 : : */
295 : : static void
296 : 2 : valent_telephony_constructed (GObject *object)
297 : : {
298 : 2 : ValentTelephony *self = VALENT_TELEPHONY (object);
299 : :
300 : 2 : G_OBJECT_CLASS (valent_telephony_parent_class)->constructed (object);
301 : :
302 : 2 : g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM,
303 : : G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
304 : : "org.freedesktop.ModemManager1",
305 : : "/org/freedesktop/ModemManager1",
306 : : NULL, NULL, NULL,
307 : : NULL,
308 : : (GAsyncReadyCallback)g_dbus_object_manager_client_new_for_bus_cb,
309 : : g_object_ref (self));
310 : 2 : }
311 : :
312 : : static void
313 : 1 : valent_telephony_finalize (GObject *object)
314 : : {
315 : 1 : ValentTelephony *self = VALENT_TELEPHONY (object);
316 : :
317 [ + - ]: 1 : g_clear_pointer (&self->modems, g_hash_table_unref);
318 : :
319 : 1 : G_OBJECT_CLASS (valent_telephony_parent_class)->finalize (object);
320 : 1 : }
321 : :
322 : : static void
323 : 2 : valent_telephony_class_init (ValentTelephonyClass *klass)
324 : : {
325 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
326 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
327 : :
328 : 2 : object_class->constructed = valent_telephony_constructed;
329 : 2 : object_class->finalize = valent_telephony_finalize;
330 : :
331 : 2 : vobject_class->destroy = valent_telephony_destroy;
332 : :
333 : : /**
334 : : * ValentTelephony::changed:
335 : : * @self: a `ValentTelephony`
336 : : *
337 : : * `ValentTelephony`::changed is emitted whenever a relevant property changes.
338 : : */
339 : 4 : signals [CHANGED] =
340 : 2 : g_signal_new ("changed",
341 : : G_TYPE_FROM_CLASS (klass),
342 : : G_SIGNAL_RUN_LAST,
343 : : 0,
344 : : NULL, NULL, NULL,
345 : : G_TYPE_NONE, 0);
346 : 2 : }
347 : :
348 : : static void
349 : 2 : valent_telephony_init (ValentTelephony *self)
350 : : {
351 : 2 : self->modems = g_hash_table_new_full (g_str_hash,
352 : : g_str_equal,
353 : : g_free,
354 : : g_object_unref);
355 : 2 : }
356 : :
357 : : /**
358 : : * valent_telephony_get_default:
359 : : *
360 : : * Get the default [class@Valent.Network].
361 : : *
362 : : * Returns: (transfer none) (not nullable): a `ValentTelephony`
363 : : *
364 : : * Since: 1.0
365 : : */
366 : : ValentTelephony *
367 : 3 : valent_telephony_get_default (void)
368 : : {
369 [ + + ]: 3 : if (default_telephony == NULL)
370 : : {
371 : 2 : default_telephony = g_object_new (VALENT_TYPE_TELEPHONY, NULL);
372 : :
373 : 2 : g_object_add_weak_pointer (G_OBJECT (default_telephony),
374 : : (gpointer)&default_telephony);
375 : : }
376 : :
377 : 3 : return default_telephony;
378 : : }
379 : :
380 : : static JsonNode *
381 : 6 : valent_telephony_serialize_modem (GDBusProxy *proxy)
382 : : {
383 : 12 : g_autoptr (JsonBuilder) builder = NULL;
384 : 6 : GVariant *value;
385 : 6 : unsigned int access_technologies;
386 : 6 : uint32_t signal_quality;
387 : 6 : gboolean signal_recent;
388 : 6 : int32_t state;
389 : 6 : const char *telephony_type;
390 : 6 : int64_t signal_strength = 0;
391 : :
392 [ + - + - : 6 : g_assert (G_IS_DBUS_PROXY (proxy));
- + - - ]
393 : :
394 : : /* Extract the relevant properties */
395 : 6 : value = g_dbus_proxy_get_cached_property (proxy, "AccessTechnologies");
396 : 6 : g_variant_get (value, "u", &access_technologies);
397 [ + - ]: 6 : g_clear_pointer (&value, g_variant_unref);
398 : :
399 : 6 : value = g_dbus_proxy_get_cached_property (proxy, "SignalQuality");
400 : 6 : g_variant_get (value, "(ub)", &signal_quality, &signal_recent);
401 [ + - ]: 6 : g_clear_pointer (&value, g_variant_unref);
402 : :
403 : 6 : value = g_dbus_proxy_get_cached_property (proxy, "State");
404 : 6 : g_variant_get (value, "i", &state);
405 [ + - ]: 6 : g_clear_pointer (&value, g_variant_unref);
406 : :
407 : : /* Convert to KDE Connect values (`networkType`, `signalStrength`) */
408 : 6 : telephony_type = get_telephony_type_string (access_technologies);
409 : :
410 [ + + ]: 6 : if (state >= MM_MODEM_STATE_ENABLED)
411 : : {
412 [ + - ]: 2 : if (signal_quality >= 100)
413 : : signal_strength = 4;
414 : : else
415 : 2 : signal_strength = (int64_t)floor (signal_quality / 20);
416 : : }
417 : :
418 : : /* Serialize to a JsonNode */
419 : 6 : builder = json_builder_new ();
420 : 6 : json_builder_begin_object (builder);
421 : 6 : json_builder_set_member_name (builder, "networkType");
422 : 6 : json_builder_add_string_value (builder, telephony_type);
423 : 6 : json_builder_set_member_name (builder, "signalStrength");
424 : 6 : json_builder_add_int_value (builder, signal_strength);
425 : 6 : json_builder_end_object (builder);
426 : :
427 [ + - ]: 6 : return json_builder_get_root (builder);
428 : : }
429 : :
430 : : /**
431 : : * valent_telephony_get_signal_strengths:
432 : : * @telephony: a `ValentTelephony`
433 : : *
434 : : * Get a serialized dictionary of the known modems' status.
435 : : *
436 : : * Returns: (transfer full) (nullable): a `JsonNode`
437 : : *
438 : : * Since: 1.0
439 : : */
440 : : JsonNode *
441 : 8 : valent_telephony_get_signal_strengths (ValentTelephony *telephony)
442 : : {
443 : 16 : g_autoptr (JsonBuilder) builder = NULL;
444 : 8 : GHashTableIter iter;
445 : 8 : gpointer key, value;
446 : 8 : unsigned int m = 0;
447 : :
448 [ - + ]: 8 : g_return_val_if_fail (VALENT_IS_TELEPHONY (telephony), NULL);
449 : :
450 : 8 : builder = json_builder_new ();
451 : 8 : json_builder_begin_object (builder);
452 : :
453 : 8 : g_hash_table_iter_init (&iter, telephony->modems);
454 : :
455 [ + + ]: 14 : while (g_hash_table_iter_next (&iter, &key, &value))
456 : : {
457 : 6 : g_autoptr (JsonNode) modem = NULL;
458 : 6 : g_autofree char *id = NULL;
459 : :
460 : 6 : id = g_strdup_printf ("%u", m++);
461 : 6 : modem = valent_telephony_serialize_modem (G_DBUS_PROXY (value));
462 : :
463 : 6 : json_builder_set_member_name (builder, id);
464 : 6 : json_builder_add_value (builder, g_steal_pointer (&modem));
465 : : }
466 : :
467 : 8 : json_builder_end_object (builder);
468 : :
469 : 8 : return json_builder_get_root (builder);
470 : : }
471 : :
|