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-connectivity_report-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <math.h>
9 : :
10 : : #include <glib/gi18n.h>
11 : : #include <gio/gio.h>
12 : : #include <json-glib/json-glib.h>
13 : : #include <valent.h>
14 : :
15 : : #include "valent-connectivity_report-plugin.h"
16 : : #include "valent-telephony.h"
17 : :
18 : :
19 : : struct _ValentConnectivityReportPlugin
20 : : {
21 : : ValentDevicePlugin parent_instance;
22 : :
23 : : /* Local Modems */
24 : : ValentTelephony *telephony;
25 : : unsigned int telephony_watch : 1;
26 : : };
27 : :
28 : : static void valent_connectivity_report_plugin_send_state (ValentConnectivityReportPlugin *self);
29 : :
30 [ + + + - ]: 87 : G_DEFINE_FINAL_TYPE (ValentConnectivityReportPlugin, valent_connectivity_report_plugin, VALENT_TYPE_DEVICE_PLUGIN)
31 : :
32 : :
33 : : /*
34 : : * Local Modems
35 : : */
36 : : static void
37 : 4 : on_telephony_changed (ValentTelephony *telephony,
38 : : ValentConnectivityReportPlugin *self)
39 : : {
40 [ - + ]: 4 : g_assert (VALENT_IS_TELEPHONY (telephony));
41 [ + - ]: 4 : g_assert (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
42 : :
43 : 4 : valent_connectivity_report_plugin_send_state (self);
44 : 4 : }
45 : :
46 : :
47 : : static void
48 : 19 : valent_connectivity_report_plugin_watch_telephony (ValentConnectivityReportPlugin *self,
49 : : gboolean watch)
50 : : {
51 [ - + ]: 19 : g_assert (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
52 : :
53 [ + + ]: 19 : if (self->telephony_watch == watch)
54 : : return;
55 : :
56 [ + + ]: 4 : if (self->telephony == NULL)
57 : 2 : self->telephony = valent_telephony_get_default ();
58 : :
59 [ + + ]: 4 : if (watch)
60 : : {
61 : 2 : g_signal_connect_object (self->telephony,
62 : : "changed",
63 : : G_CALLBACK (on_telephony_changed),
64 : : self,
65 : : G_CONNECT_DEFAULT);
66 : 2 : self->telephony_watch = TRUE;
67 : : }
68 : : else
69 : : {
70 : 2 : g_signal_handlers_disconnect_by_data (self->telephony, self);
71 : 2 : self->telephony_watch = FALSE;
72 : : }
73 : : }
74 : :
75 : : static void
76 : 4 : valent_connectivity_report_plugin_send_state (ValentConnectivityReportPlugin *self)
77 : : {
78 : 4 : GSettings *settings;
79 : 4 : g_autoptr (JsonBuilder) builder = NULL;
80 [ - + ]: 4 : g_autoptr (JsonNode) packet = NULL;
81 [ + - - - ]: 4 : g_autoptr (JsonNode) signal_node = NULL;
82 : :
83 [ - + ]: 4 : g_return_if_fail (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
84 : :
85 : 4 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
86 : :
87 [ + - ]: 4 : if (!g_settings_get_boolean (settings, "share-state"))
88 : : return;
89 : :
90 : 4 : signal_node = valent_telephony_get_signal_strengths (self->telephony);
91 : :
92 : 4 : valent_packet_init (&builder, "kdeconnect.connectivity_report");
93 : 4 : json_builder_set_member_name (builder, "signalStrengths");
94 : 4 : json_builder_add_value (builder, g_steal_pointer (&signal_node));
95 : 4 : packet = valent_packet_end (&builder);
96 : :
97 [ + - ]: 4 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
98 : : }
99 : :
100 : :
101 : : /*
102 : : * Remote Modems
103 : : */
104 : : static const char *
105 : 8 : get_network_type_icon (const char *network_type)
106 : : {
107 [ + + ]: 8 : if (g_str_equal (network_type, "GSM") ||
108 [ + - ]: 7 : g_str_equal (network_type, "CDMA") ||
109 [ + - ]: 7 : g_str_equal (network_type, "iDEN"))
110 : : return "network-cellular-2g-symbolic";
111 : :
112 [ + + ]: 7 : if (g_str_equal (network_type, "UMTS") ||
113 [ + - ]: 6 : g_str_equal (network_type, "CDMA2000"))
114 : : return "network-cellular-3g-symbolic";
115 : :
116 [ + + ]: 6 : if (g_str_equal (network_type, "EDGE"))
117 : : return "network-cellular-edge-symbolic";
118 : :
119 [ + + ]: 5 : if (g_str_equal (network_type, "GPRS"))
120 : : return "network-cellular-gprs-symbolic";
121 : :
122 [ + + ]: 4 : if (g_str_equal (network_type, "HSPA"))
123 : : return "network-cellular-hspa-symbolic";
124 : :
125 [ + + ]: 3 : if (g_str_equal (network_type, "LTE"))
126 : : return "network-cellular-4g-symbolic";
127 : :
128 [ + + ]: 2 : if (g_str_equal (network_type, "5G"))
129 : 1 : return "network-cellular-5g-symbolic";
130 : :
131 : : return "network-cellular-symbolic";
132 : : }
133 : :
134 : : static const char *
135 : 9 : get_signal_strength_icon (double signal_strength)
136 : : {
137 [ + + ]: 9 : if (signal_strength >= 4.0)
138 : : return "network-cellular-signal-excellent-symbolic";
139 : :
140 [ + + ]: 6 : if (signal_strength >= 3.0)
141 : : return "network-cellular-signal-good-symbolic";
142 : :
143 [ + + ]: 5 : if (signal_strength >= 2.0)
144 : : return "network-cellular-signal-ok-symbolic";
145 : :
146 [ + + ]: 4 : if (signal_strength >= 1.0)
147 : : return "network-cellular-signal-weak-symbolic";
148 : :
149 [ + + ]: 3 : if (signal_strength >= 0.0)
150 : 2 : return "network-cellular-signal-none-symbolic";
151 : :
152 : : return "network-cellular-offline-symbolic";
153 : : }
154 : :
155 : : static void
156 : 9 : get_status_labels (double signal_strength,
157 : : char **status_title,
158 : : char **status_body)
159 : : {
160 [ + + ]: 9 : if (signal_strength >= 1.0)
161 : : {
162 : : /* TRANSLATORS: When the mobile network signal is available */
163 [ - + ]: 6 : *status_title = g_strdup (_("Mobile Network"));
164 : : /* TRANSLATORS: The mobile network signal strength (e.g. "Signal Strength (25%)") */
165 : 6 : *status_body = g_strdup_printf (_("Signal Strength %f%%"),
166 : : floor (signal_strength * 25.0));
167 : : }
168 [ + + ]: 3 : else if (signal_strength >= 0.0)
169 : : {
170 : : /* TRANSLATORS: When no mobile service is available */
171 [ - + ]: 2 : *status_title = g_strdup (_("No Service"));
172 : : /* TRANSLATORS: When no mobile network signal is available */
173 [ - + ]: 4 : *status_body = g_strdup (_("No mobile network service"));
174 : : }
175 : : else
176 : : {
177 : : /* TRANSLATORS: When no mobile service is available */
178 [ - + ]: 1 : *status_title = g_strdup (_("No Service"));
179 : : /* TRANSLATORS: When the device is missing a SIM card */
180 [ - + ]: 2 : *status_body = g_strdup (_("No SIM"));
181 : : }
182 : 9 : }
183 : :
184 : : static void
185 : 9 : valent_connectivity_report_plugin_handle_connectivity_report (ValentConnectivityReportPlugin *self,
186 : : JsonNode *packet)
187 : : {
188 : 9 : GAction *action;
189 : 9 : GVariant *state;
190 : 9 : GSettings *settings;
191 : 9 : GVariantBuilder builder;
192 : 9 : GVariantBuilder signals_builder;
193 : 9 : JsonObject *signal_strengths;
194 : 9 : JsonObjectIter iter;
195 : 9 : const char *signal_id;
196 : 9 : JsonNode *signal_node;
197 : 9 : double average_strength = 0.0;
198 : 9 : double n_nodes = 0;
199 : 9 : gboolean is_online = FALSE;
200 : 9 : const char *status_icon;
201 : 9 : g_autofree char *status_title = NULL;
202 : 9 : g_autofree char *status_body = NULL;
203 : :
204 [ - + ]: 9 : g_assert (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
205 [ + - ]: 9 : g_assert (VALENT_IS_PACKET (packet));
206 : :
207 [ - + ]: 9 : if (!valent_packet_get_object (packet, "signalStrengths", &signal_strengths))
208 : : {
209 : 0 : g_debug ("%s(): expected \"signalStrengths\" field holding an object",
210 : : G_STRFUNC);
211 : 0 : return;
212 : : }
213 : :
214 : 9 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
215 : :
216 : : /* Add each signal */
217 : 9 : g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
218 : 9 : g_variant_builder_init (&signals_builder, G_VARIANT_TYPE_VARDICT);
219 : :
220 : 9 : json_object_iter_init (&iter, signal_strengths);
221 : :
222 [ + + ]: 17 : while (json_object_iter_next (&iter, &signal_id, &signal_node))
223 : : {
224 : 8 : GVariantBuilder signal_builder;
225 : 8 : JsonObject *signal_obj;
226 : 8 : const char *network_type;
227 : 8 : int64_t signal_strength;
228 : 8 : const char *icon_name;
229 : :
230 [ - + ]: 8 : if G_UNLIKELY (json_node_get_value_type (signal_node) != JSON_TYPE_OBJECT)
231 : : {
232 : 0 : g_debug ("%s(): expected entry value holding an object",
233 : : G_STRFUNC);
234 : 0 : continue;
235 : : }
236 : :
237 : : /* Extract the signal information */
238 : 8 : signal_obj = json_node_get_object (signal_node);
239 : 8 : network_type = json_object_get_string_member_with_default (signal_obj,
240 : : "networkType",
241 : : "Unknown");
242 : 8 : signal_strength = json_object_get_int_member_with_default (signal_obj,
243 : : "signalStrength",
244 : : -1);
245 : 8 : icon_name = get_network_type_icon (network_type);
246 : :
247 : : /* Ignore offline modems (`-1`) when determining the average strength */
248 [ + - ]: 8 : if (signal_strength >= 0)
249 : : {
250 : 8 : average_strength = (n_nodes * average_strength + signal_strength) /
251 : 8 : (n_nodes + 1);
252 : 8 : n_nodes += 1;
253 : 8 : is_online = TRUE;
254 : : }
255 : :
256 : : /* Add the signal to the `signal_strengths` dictionary */
257 : 8 : g_variant_builder_init (&signal_builder, G_VARIANT_TYPE_VARDICT);
258 : 8 : g_variant_builder_add (&signal_builder, "{sv}", "network-type",
259 : : g_variant_new_string (network_type));
260 : 8 : g_variant_builder_add (&signal_builder, "{sv}", "signal-strength",
261 : : g_variant_new_int64 (signal_strength));
262 : 8 : g_variant_builder_add (&signal_builder, "{sv}", "icon-name",
263 : : g_variant_new_string (icon_name));
264 : 8 : g_variant_builder_add (&signals_builder, "{sv}", signal_id,
265 : : g_variant_builder_end (&signal_builder));
266 : : }
267 : :
268 : 9 : g_variant_builder_add (&builder, "{sv}", "signal-strengths",
269 : : g_variant_builder_end (&signals_builder));
270 : :
271 : : /* Set the status properties */
272 [ + + ]: 9 : status_icon = get_signal_strength_icon (is_online ? average_strength : -1);
273 : 9 : get_status_labels (is_online ? average_strength : -1,
274 : : &status_title,
275 : : &status_body);
276 : :
277 : 9 : g_variant_builder_add (&builder, "{sv}", "icon-name",
278 : : g_variant_new_string (status_icon));
279 : 9 : g_variant_builder_add (&builder, "{sv}", "title",
280 : : g_variant_new_string (status_title));
281 : 9 : g_variant_builder_add (&builder, "{sv}", "body",
282 : : g_variant_new_string (status_body));
283 : :
284 : 9 : state = g_variant_builder_end (&builder);
285 : :
286 : : /* Update the GAction */
287 : 9 : action = g_action_map_lookup_action (G_ACTION_MAP (self), "state");
288 : 18 : g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
289 : 9 : json_object_get_size (signal_strengths) > 0);
290 : 9 : g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
291 : :
292 : : /* Notify if necessary */
293 [ + + ]: 9 : if (average_strength > 0.0)
294 : : {
295 : 6 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self),
296 : : "offline");
297 : : }
298 [ + - ]: 3 : else if (g_settings_get_boolean (settings, "offline-notification"))
299 : : {
300 : 3 : ValentDevice *device;
301 : 9 : g_autoptr (GNotification) notification = NULL;
302 [ + - ]: 3 : g_autoptr (GIcon) icon = NULL;
303 [ + - ]: 3 : g_autofree char *title = NULL;
304 : 3 : g_autofree char *body = NULL;
305 : 3 : const char *device_name;
306 : :
307 : 3 : device = valent_resource_get_source (VALENT_RESOURCE (self));
308 : 3 : device_name = valent_device_get_name (device);
309 : :
310 : : /* TRANSLATORS: The connectivity notification title (e.g. "PinePhone: No Service") */
311 : 3 : title = g_strdup_printf (_("%s: %s"), device_name, status_title);
312 : : /* TRANSLATORS: The connectivity notification body (e.g. "No mobile network service") */
313 [ - + ]: 3 : body = g_strdup (status_body);
314 : 3 : icon = g_themed_icon_new (status_icon);
315 : :
316 : 3 : notification = g_notification_new (title);
317 : 3 : g_notification_set_body (notification, body);
318 : 3 : g_notification_set_icon (notification, icon);
319 : 3 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
320 : : "offline",
321 : : notification);
322 : : }
323 : : }
324 : :
325 : : /*
326 : : * GActions
327 : : */
328 : : static void
329 : 0 : state_action (GSimpleAction *action,
330 : : GVariant *parameter,
331 : : gpointer user_data)
332 : : {
333 : : // No-op to make the state read-only
334 : 0 : }
335 : :
336 : : static const GActionEntry actions[] = {
337 : : {"state", NULL, NULL, "@a{sv} {}", state_action},
338 : : };
339 : :
340 : : /*
341 : : * ValentDevicePlugin
342 : : */
343 : : static void
344 : 9 : valent_connectivity_report_plugin_update_state (ValentDevicePlugin *plugin,
345 : : ValentDeviceState state)
346 : : {
347 : 9 : ValentConnectivityReportPlugin *self = VALENT_CONNECTIVITY_REPORT_PLUGIN (plugin);
348 : 9 : gboolean available;
349 : :
350 [ - + ]: 9 : g_assert (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
351 : :
352 [ + + ]: 9 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
353 [ + + ]: 4 : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
354 : :
355 : 9 : if (available)
356 : : {
357 : 2 : valent_connectivity_report_plugin_watch_telephony (self, TRUE);
358 : : }
359 : : else
360 : : {
361 : 7 : valent_connectivity_report_plugin_watch_telephony (self, FALSE);
362 : 7 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
363 : : }
364 : 9 : }
365 : :
366 : : static void
367 : 9 : valent_connectivity_report_plugin_handle_packet (ValentDevicePlugin *plugin,
368 : : const char *type,
369 : : JsonNode *packet)
370 : : {
371 : 9 : ValentConnectivityReportPlugin *self = VALENT_CONNECTIVITY_REPORT_PLUGIN (plugin);
372 : :
373 [ - + ]: 9 : g_assert (VALENT_IS_CONNECTIVITY_REPORT_PLUGIN (self));
374 [ + - ]: 9 : g_assert (type != NULL);
375 [ + - ]: 9 : g_assert (VALENT_IS_PACKET (packet));
376 : :
377 : : /* A remote connectivity report */
378 [ + - ]: 9 : if (g_str_equal (type, "kdeconnect.connectivity_report"))
379 : 9 : valent_connectivity_report_plugin_handle_connectivity_report (self, packet);
380 : : else
381 : 9 : g_assert_not_reached ();
382 : 9 : }
383 : :
384 : : /*
385 : : * ValentObject
386 : : */
387 : : static void
388 : 10 : valent_connectivity_report_plugin_destroy (ValentObject *object)
389 : : {
390 : 10 : ValentConnectivityReportPlugin *self = VALENT_CONNECTIVITY_REPORT_PLUGIN (object);
391 : :
392 : 10 : valent_connectivity_report_plugin_watch_telephony (self, FALSE);
393 : :
394 : 10 : VALENT_OBJECT_CLASS (valent_connectivity_report_plugin_parent_class)->destroy (object);
395 : 10 : }
396 : :
397 : : /*
398 : : * GObject
399 : : */
400 : : static void
401 : 5 : valent_connectivity_report_plugin_constructed (GObject *object)
402 : : {
403 : 5 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
404 : :
405 : 5 : G_OBJECT_CLASS (valent_connectivity_report_plugin_parent_class)->constructed (object);
406 : :
407 : 5 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
408 : : actions,
409 : : G_N_ELEMENTS (actions),
410 : : plugin);
411 : 5 : }
412 : :
413 : : static void
414 : 11 : valent_connectivity_report_plugin_class_init (ValentConnectivityReportPluginClass *klass)
415 : : {
416 : 11 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
417 : 11 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
418 : 11 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
419 : :
420 : 11 : object_class->constructed = valent_connectivity_report_plugin_constructed;
421 : :
422 : 11 : vobject_class->destroy = valent_connectivity_report_plugin_destroy;
423 : :
424 : 11 : plugin_class->handle_packet = valent_connectivity_report_plugin_handle_packet;
425 : 11 : plugin_class->update_state = valent_connectivity_report_plugin_update_state;
426 : : }
427 : :
428 : : static void
429 : 5 : valent_connectivity_report_plugin_init (ValentConnectivityReportPlugin *self)
430 : : {
431 : 5 : }
432 : :
|