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-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <gio/gio.h>
10 : : #include <json-glib/json-glib.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-telephony-plugin.h"
14 : :
15 : :
16 : : struct _ValentTelephonyPlugin
17 : : {
18 : : ValentDevicePlugin parent_instance;
19 : :
20 : : gpointer prev_input;
21 : : gpointer prev_output;
22 : : gboolean prev_paused;
23 : : };
24 : :
25 [ + + + - ]: 51 : G_DEFINE_FINAL_TYPE (ValentTelephonyPlugin, valent_telephony_plugin, VALENT_TYPE_DEVICE_PLUGIN)
26 : :
27 : :
28 : : /*
29 : : * StreamState Helpers
30 : : */
31 : : typedef struct
32 : : {
33 : : GWeakRef stream;
34 : :
35 : : unsigned int current_level;
36 : : unsigned int current_muted : 1;
37 : : unsigned int original_level;
38 : : unsigned int original_muted : 1;
39 : : } StreamState;
40 : :
41 : : static StreamState *
42 : 8 : stream_state_new (ValentMixerStream *stream,
43 : : int level)
44 : : {
45 : 8 : StreamState *state;
46 : :
47 : 8 : state = g_new0 (StreamState, 1);
48 : 8 : g_weak_ref_init (&state->stream, stream);
49 : 8 : state->original_level = valent_mixer_stream_get_level (stream);
50 : 8 : state->original_muted = valent_mixer_stream_get_muted (stream);
51 : :
52 [ - + ]: 8 : if (level == 0)
53 : : {
54 : 0 : state->current_level = valent_mixer_stream_get_level (stream);
55 : 0 : state->current_muted = TRUE;
56 : :
57 : 0 : valent_mixer_stream_set_muted (stream, TRUE);
58 : : }
59 [ + + ]: 8 : else if (level > 0)
60 : : {
61 : 4 : state->current_level = level;
62 : 4 : state->current_muted = valent_mixer_stream_get_muted (stream);
63 : :
64 : 4 : valent_mixer_stream_set_level (stream, level);
65 : : }
66 : :
67 : 8 : return state;
68 : : }
69 : :
70 : : static void
71 : 4 : stream_state_update (StreamState *state,
72 : : ValentMixerStream *stream,
73 : : int level)
74 : : {
75 : 8 : g_autoptr (ValentMixerStream) current_stream = NULL;
76 : :
77 : : /* If the active stream has changed, bail instead of guessing what to do */
78 [ + + ]: 4 : if ((current_stream = g_weak_ref_get (&state->stream)) != stream)
79 : : {
80 : 1 : g_weak_ref_set (&state->stream, NULL);
81 [ + - ]: 1 : return;
82 : : }
83 : :
84 [ + - ]: 3 : if (level == 0)
85 : : {
86 : 3 : state->current_muted = TRUE;
87 : 3 : valent_mixer_stream_set_muted (stream, TRUE);
88 : : }
89 [ # # ]: 0 : else if (level > 0)
90 : : {
91 : 0 : state->current_level = level;
92 : 0 : valent_mixer_stream_set_level (stream, level);
93 : : }
94 : : }
95 : :
96 : : static inline void
97 : 8 : stream_state_restore (gpointer data)
98 : : {
99 : 8 : StreamState *state = data;
100 : 16 : g_autoptr (ValentMixerStream) stream = NULL;
101 : :
102 [ + + ]: 8 : if ((stream = g_weak_ref_get (&state->stream)) != NULL)
103 : : {
104 [ + + ]: 7 : if (valent_mixer_stream_get_level (stream) == state->current_level)
105 : 3 : valent_mixer_stream_set_level (stream, state->original_level);
106 : :
107 [ + - ]: 7 : if (valent_mixer_stream_get_muted (stream) == state->current_muted)
108 : 7 : valent_mixer_stream_set_muted (stream, state->original_muted);
109 : : }
110 : :
111 : 8 : g_weak_ref_clear (&state->stream);
112 [ + - + + ]: 8 : g_clear_pointer (&state, g_free);
113 : 8 : }
114 : :
115 : : static inline void
116 : 0 : stream_state_free (gpointer data)
117 : : {
118 : 0 : StreamState *state = data;
119 : :
120 : 0 : g_weak_ref_clear (&state->stream);
121 [ # # ]: 0 : g_clear_pointer (&state, g_free);
122 : 0 : }
123 : :
124 : : static void
125 : 4 : valent_telephony_plugin_restore_media_state (ValentTelephonyPlugin *self)
126 : : {
127 [ + - ]: 4 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (self));
128 : :
129 [ + - ]: 4 : g_clear_pointer (&self->prev_output, stream_state_restore);
130 [ + - ]: 4 : g_clear_pointer (&self->prev_input, stream_state_restore);
131 : :
132 [ + + ]: 4 : if (self->prev_paused)
133 : : {
134 : 2 : ValentMedia *media;
135 : :
136 : 2 : media = valent_media_get_default ();
137 : 2 : valent_media_unpause (media);
138 : 2 : self->prev_paused = FALSE;
139 : : }
140 : 4 : }
141 : :
142 : : static void
143 : 6 : valent_telephony_plugin_update_media_state (ValentTelephonyPlugin *self,
144 : : const char *event)
145 : : {
146 : 6 : GSettings *settings;
147 : 6 : ValentMixer *mixer;
148 : 6 : ValentMixerStream *stream;
149 : 6 : int output_level;
150 : 6 : int input_level;
151 : 6 : gboolean pause = FALSE;
152 : :
153 [ + - ]: 6 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (self));
154 [ + - - + ]: 6 : g_assert (event != NULL && *event != '\0');
155 : :
156 : 6 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
157 : :
158 : : /* Retrieve the user preference for this event */
159 [ + + ]: 6 : if (g_str_equal (event, "ringing"))
160 : : {
161 : 4 : output_level = g_settings_get_int (settings, "ringing-volume");
162 : 4 : input_level = g_settings_get_int (settings, "ringing-microphone");
163 : 4 : pause = g_settings_get_boolean (settings, "ringing-pause");
164 : : }
165 [ + - ]: 2 : else if (g_str_equal (event, "talking"))
166 : : {
167 : 2 : output_level = g_settings_get_int (settings, "talking-volume");
168 : 2 : input_level = g_settings_get_int (settings, "talking-microphone");
169 : 2 : pause = g_settings_get_boolean (settings, "talking-pause");
170 : : }
171 : : else
172 : : {
173 : 0 : g_return_if_reached ();
174 : : }
175 : :
176 : : /* Speakers & Microphone */
177 : 6 : mixer = valent_mixer_get_default ();
178 : :
179 [ + - ]: 6 : if ((stream = valent_mixer_get_default_output (mixer)) != NULL)
180 : : {
181 [ + + ]: 6 : if (self->prev_output == NULL)
182 : 4 : self->prev_output = stream_state_new (stream, output_level);
183 : : else
184 : 2 : stream_state_update (self->prev_output, stream, output_level);
185 : : }
186 : :
187 [ + - ]: 6 : if ((stream = valent_mixer_get_default_input (mixer)) != NULL)
188 : : {
189 [ + + ]: 6 : if (self->prev_input == NULL)
190 : 4 : self->prev_input = stream_state_new (stream, input_level);
191 : : else
192 : 2 : stream_state_update (self->prev_input, stream, input_level);
193 : : }
194 : :
195 : : /* Media Players */
196 [ + + ]: 6 : if (pause)
197 : : {
198 : 2 : ValentMedia *media;
199 : :
200 : 2 : media = valent_media_get_default ();
201 : 2 : valent_media_pause (media);
202 : 2 : self->prev_paused = TRUE;
203 : : }
204 : : }
205 : :
206 : : static GIcon *
207 : 6 : valent_telephony_plugin_get_event_icon (JsonNode *packet,
208 : : const char *event)
209 : : {
210 : 6 : const char *phone_thumbnail = NULL;
211 : :
212 [ + - ]: 6 : g_assert (VALENT_IS_PACKET (packet));
213 : :
214 [ + - ]: 6 : if (valent_packet_get_string (packet, "phoneThumbnail", &phone_thumbnail))
215 : : {
216 : 6 : g_autoptr (GdkPixbufLoader) loader = NULL;
217 : 6 : GdkPixbuf *pixbuf = NULL;
218 [ + - - - ]: 6 : g_autoptr (GError) error = NULL;
219 [ - + - - ]: 6 : g_autofree unsigned char *data = NULL;
220 : 6 : size_t dlen;
221 : :
222 : 6 : data = g_base64_decode (phone_thumbnail, &dlen);
223 : 6 : loader = gdk_pixbuf_loader_new();
224 : :
225 [ - + - + ]: 12 : if (gdk_pixbuf_loader_write (loader, data, dlen, &error) &&
226 : 6 : gdk_pixbuf_loader_close (loader, &error))
227 : 6 : pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
228 : :
229 [ - + ]: 6 : if (error != NULL)
230 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
231 : :
232 [ + - ]: 6 : if (pixbuf != NULL)
233 : 6 : return G_ICON (g_object_ref (pixbuf));
234 : : }
235 : :
236 [ # # ]: 0 : if (g_str_equal (event, "ringing"))
237 : 0 : return g_themed_icon_new ("call-incoming-symbolic");
238 : :
239 [ # # ]: 0 : if (g_str_equal (event, "talking"))
240 : 0 : return g_themed_icon_new ("call-start-symbolic");
241 : :
242 [ # # ]: 0 : if (g_str_equal (event, "missedCall"))
243 : 0 : return g_themed_icon_new ("call-missed-symbolic");
244 : :
245 : : return NULL;
246 : : }
247 : :
248 : : static void
249 : 10 : valent_telephony_plugin_handle_telephony (ValentTelephonyPlugin *self,
250 : : JsonNode *packet)
251 : : {
252 : 10 : ValentDevice *device;
253 : 10 : const char *event;
254 : 10 : const char *sender;
255 : 10 : g_autoptr (GNotification) notification = NULL;
256 [ + - ]: 10 : g_autoptr (GIcon) icon = NULL;
257 : :
258 [ + - ]: 10 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (self));
259 [ - + ]: 10 : g_assert (VALENT_IS_PACKET (packet));
260 : :
261 [ - + ]: 10 : if (!valent_packet_get_string (packet, "event", &event))
262 : : {
263 : 0 : g_debug ("%s(): expected \"event\" field holding a string",
264 : : G_STRFUNC);
265 : 0 : return;
266 : : }
267 : :
268 : : /* Currently, only "ringing" and "talking" events are supported */
269 [ + + + - ]: 10 : if (!g_str_equal (event, "ringing") && !g_str_equal (event, "talking"))
270 : : {
271 : : VALENT_NOTE ("TODO: \"%s\" event", event);
272 : : return;
273 : : }
274 : :
275 : : /* Sender*/
276 [ + + + - ]: 14 : if (!valent_packet_get_string (packet, "contactName", &sender) &&
277 : 4 : !valent_packet_get_string (packet, "phoneNumber", &sender))
278 : 4 : sender = _("Unknown Contact");
279 : :
280 : : /* The sender is injected into the notification ID, since it's possible an
281 : : * event could occur for multiple callers concurrently.
282 : : *
283 : : * Because we only support voice events, we can be certain that subsequent
284 : : * events from the same sender supersede previous events, and replace the
285 : : * older notifications.
286 : : */
287 : 10 : device = valent_extension_get_object (VALENT_EXTENSION (self));
288 : :
289 : : /* This is a cancelled event */
290 [ + + ]: 10 : if (valent_packet_check_field (packet, "isCancel"))
291 : : {
292 : 4 : valent_telephony_plugin_restore_media_state (self);
293 : 4 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self),
294 : : sender);
295 : 4 : return;
296 : : }
297 : :
298 : : /* Adjust volume/pause media */
299 : 6 : valent_telephony_plugin_update_media_state (self, event);
300 : :
301 : : /* Notify user */
302 : 6 : notification = g_notification_new (sender);
303 : 6 : icon = valent_telephony_plugin_get_event_icon (packet, event);
304 : 6 : g_notification_set_icon (notification, icon);
305 : :
306 [ + + ]: 6 : if (g_str_equal (event, "ringing"))
307 : : {
308 : 4 : g_notification_set_body (notification, _("Incoming call"));
309 : 4 : valent_notification_add_device_button (notification,
310 : : device,
311 : 4 : _("Mute"),
312 : : "telephony.mute-call",
313 : : NULL);
314 : 4 : g_notification_set_priority (notification,
315 : : G_NOTIFICATION_PRIORITY_URGENT);
316 : : }
317 [ + - ]: 2 : else if (g_str_equal (event, "talking"))
318 : : {
319 : 2 : g_notification_set_body (notification, _("Ongoing call"));
320 : : }
321 : :
322 [ + - ]: 6 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
323 : : sender,
324 : : notification);
325 : : }
326 : :
327 : : static void
328 : 1 : valent_telephony_plugin_mute_call (ValentTelephonyPlugin *self)
329 : : {
330 : 2 : g_autoptr (JsonNode) packet = NULL;
331 : :
332 [ + - ]: 1 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (self));
333 : :
334 : 1 : packet = valent_packet_new ("kdeconnect.telephony.request_mute");
335 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
336 : 1 : }
337 : :
338 : : /*
339 : : * GActions
340 : : */
341 : : static void
342 : 1 : mute_call_action (GSimpleAction *action,
343 : : GVariant *parameter,
344 : : gpointer user_data)
345 : : {
346 : 1 : ValentTelephonyPlugin *self = VALENT_TELEPHONY_PLUGIN (user_data);
347 : :
348 [ + - ]: 1 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (self));
349 : :
350 : 1 : valent_telephony_plugin_mute_call (self);
351 : 1 : }
352 : :
353 : : static const GActionEntry actions[] = {
354 : : {"mute-call", mute_call_action, NULL, NULL, NULL}
355 : : };
356 : :
357 : : /*
358 : : * ValentDevicePlugin
359 : : */
360 : : static void
361 : 13 : valent_telephony_plugin_update_state (ValentDevicePlugin *plugin,
362 : : ValentDeviceState state)
363 : : {
364 : 13 : ValentTelephonyPlugin *self = VALENT_TELEPHONY_PLUGIN (plugin);
365 : 13 : gboolean available;
366 : :
367 [ + - ]: 13 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (plugin));
368 : :
369 : 13 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
370 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
371 : :
372 : : /* Clear the stream state, but don't restore it as there may still be an
373 : : * event in progress. */
374 [ + + ]: 13 : if (!available)
375 : : {
376 [ - + ]: 9 : g_clear_pointer (&self->prev_output, stream_state_free);
377 [ - + ]: 9 : g_clear_pointer (&self->prev_input, stream_state_free);
378 : : }
379 : :
380 : 13 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
381 : 13 : }
382 : :
383 : : static void
384 : 10 : valent_telephony_plugin_handle_packet (ValentDevicePlugin *plugin,
385 : : const char *type,
386 : : JsonNode *packet)
387 : : {
388 : 10 : ValentTelephonyPlugin *self = VALENT_TELEPHONY_PLUGIN (plugin);
389 : :
390 [ + - ]: 10 : g_assert (VALENT_IS_TELEPHONY_PLUGIN (plugin));
391 [ - + ]: 10 : g_assert (type != NULL);
392 [ - + ]: 10 : g_assert (VALENT_IS_PACKET (packet));
393 : :
394 [ + - ]: 10 : if (g_str_equal (type, "kdeconnect.telephony"))
395 : 10 : valent_telephony_plugin_handle_telephony (self, packet);
396 : : else
397 : 10 : g_assert_not_reached ();
398 : 10 : }
399 : :
400 : : /*
401 : : * ValentObject
402 : : */
403 : : static void
404 : 8 : valent_telephony_plugin_destroy (ValentObject *object)
405 : : {
406 : 8 : ValentTelephonyPlugin *self = VALENT_TELEPHONY_PLUGIN (object);
407 : :
408 [ - + ]: 8 : g_clear_pointer (&self->prev_output, stream_state_free);
409 [ - + ]: 8 : g_clear_pointer (&self->prev_input, stream_state_free);
410 : :
411 : 8 : VALENT_OBJECT_CLASS (valent_telephony_plugin_parent_class)->destroy (object);
412 : 8 : }
413 : :
414 : : /*
415 : : * GObject
416 : : */
417 : : static void
418 : 4 : valent_telephony_plugin_constructed (GObject *object)
419 : : {
420 : 4 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
421 : :
422 : 4 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
423 : : actions,
424 : : G_N_ELEMENTS (actions),
425 : : plugin);
426 : :
427 : 4 : G_OBJECT_CLASS (valent_telephony_plugin_parent_class)->constructed (object);
428 : 4 : }
429 : :
430 : : static void
431 : 2 : valent_telephony_plugin_class_init (ValentTelephonyPluginClass *klass)
432 : : {
433 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
434 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
435 : 2 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
436 : :
437 : 2 : object_class->constructed = valent_telephony_plugin_constructed;
438 : :
439 : 2 : vobject_class->destroy = valent_telephony_plugin_destroy;
440 : :
441 : 2 : plugin_class->handle_packet = valent_telephony_plugin_handle_packet;
442 : 2 : plugin_class->update_state = valent_telephony_plugin_update_state;
443 : : }
444 : :
445 : : static void
446 : 4 : valent_telephony_plugin_init (ValentTelephonyPlugin *self)
447 : : {
448 : 4 : }
449 : :
|