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