1 /* $Id$ */
2 /* Copyright (c) 2011-2015 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Phone */
4 /* This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16
17
18 #include <stdio.h>
19 #include <string.h>
20 #include <errno.h>
21 #include <libintl.h>
22 #include <gtk/gtk.h>
23 #include <System.h>
24 #include "Phone.h"
25 #define max(a, b) ((a) > (b) ? (a) : (b))
26
27
28 /* Profiles */
29 /* private */
30 /* types */
31 typedef enum _ProfileType
32 {
33 PROFILE_TYPE_GENERAL = 0,
34 PROFILE_TYPE_BEEP,
35 PROFILE_TYPE_SILENT,
36 PROFILE_TYPE_OFFLINE
37 } ProfileType;
38 #define PROFILE_TYPE_LAST PROFILE_TYPE_OFFLINE
39 #define PROFILE_TYPE_COUNT (PROFILE_TYPE_LAST + 1)
40
41 typedef enum _ProfileVolume
42 {
43 PROFILE_VOLUME_SILENT = 0,
44 PROFILE_VOLUME_25 = 25,
45 PROFILE_VOLUME_50 = 50,
46 PROFILE_VOLUME_75 = 75,
47 PROFILE_VOLUME_100 = 100,
48 PROFILE_VOLUME_ASC = -1
49 } ProfileVolume;
50
51 typedef struct _ProfileDefinition
52 {
53 char const * name;
54 gboolean online;
55 ProfileVolume volume;
56 gboolean vibrate;
57 char const * sample;
58 } ProfileDefinition;
59
60 typedef struct _PhonePlugin
61 {
62 PhonePluginHelper * helper;
63
64 guint source;
65
66 /* profiles */
67 ProfileDefinition * profiles;
68 size_t profiles_cnt;
69 size_t profiles_cur;
70
71 /* vibrator */
72 int vibrator;
73
74 /* settings */
75 GtkWidget * pr_window;
76 GtkWidget * pr_combo;
77 GtkWidget * pr_online;
78 GtkWidget * pr_volume;
79 GtkWidget * pr_vibrator;
80 } Profiles;
81
82 /* constants */
83 #define PROFILE_VIBRATOR_LOOP 500
84
85 /* variables */
86 static ProfileDefinition _profiles_definitions[PROFILE_TYPE_COUNT] =
87 {
88 { "General", TRUE, PROFILE_VOLUME_ASC, TRUE, NULL },
89 { "Beep", TRUE, PROFILE_VOLUME_25, TRUE, "beep" },
90 { "Silent", TRUE, PROFILE_VOLUME_SILENT, TRUE, NULL },
91 { "Offline", FALSE, PROFILE_VOLUME_SILENT, FALSE, NULL }
92 };
93
94 /* prototypes */
95 /* plug-in */
96 static Profiles * _profiles_init(PhonePluginHelper * helper);
97 static void _profiles_destroy(Profiles * profiles);
98 static int _profiles_event(Profiles * profiles, PhoneEvent * event);
99 static void _profiles_settings(Profiles * profiles);
100
101 /* accessors */
102 static int _profiles_set(Profiles * profiles, ProfileType type);
103
104 /* useful */
105 static void _profiles_apply(Profiles * profiles, ProfileType type);
106 static void _profiles_play(Profiles * profiles, char const * sound,
107 int vibrator);
108 static void _profiles_switch(Profiles * profiles, ProfileType type);
109
110 /* callbacks */
111 static gboolean _profiles_on_vibrate(gpointer data);
112
113
114 /* public */
115 /* variables */
116 PhonePluginDefinition plugin =
117 {
118 "Profiles",
119 "system-config-users",
120 NULL,
121 _profiles_init,
122 _profiles_destroy,
123 _profiles_event,
124 _profiles_settings
125 };
126
127
128 /* private */
129 /* functions */
130 /* profiles_init */
_profiles_init(PhonePluginHelper * helper)131 static Profiles * _profiles_init(PhonePluginHelper * helper)
132 {
133 Profiles * profiles;
134 char const * p;
135 size_t i;
136
137 #ifdef DEBUG
138 fprintf(stderr, "DEBUG: %s()\n", __func__);
139 #endif
140 if((profiles = object_new(sizeof(*profiles))) == NULL)
141 return NULL;
142 profiles->helper = helper;
143 profiles->source = 0;
144 profiles->profiles = _profiles_definitions;
145 profiles->profiles_cnt = sizeof(_profiles_definitions)
146 / sizeof(*_profiles_definitions);
147 profiles->profiles_cur = 0;
148 if((p = helper->config_get(helper->phone, "profiles", "default"))
149 != NULL)
150 for(i = 0; i < profiles->profiles_cnt; i++)
151 if(strcmp(profiles->profiles[i].name, p) == 0)
152 {
153 profiles->profiles_cur = i;
154 break;
155 }
156 profiles->vibrator = 0;
157 profiles->pr_window = NULL;
158 return profiles;
159 }
160
161
162 /* profiles_destroy */
_profiles_destroy(Profiles * profiles)163 static void _profiles_destroy(Profiles * profiles)
164 {
165 #ifdef DEBUG
166 fprintf(stderr, "DEBUG: %s()\n", __func__);
167 #endif
168 if(profiles->source != 0)
169 g_source_remove(profiles->source);
170 if(profiles->pr_window != NULL)
171 gtk_widget_destroy(profiles->pr_window);
172 object_delete(profiles);
173 }
174
175
176 /* profiles_event */
177 static int _event_key_tone(Profiles * profiles);
178 static int _event_modem_event(Profiles * profiles, ModemEvent * event);
179 static int _event_offline(Profiles * profiles);
180 static int _event_starting(Profiles * profiles);
181 static int _event_stopping(Profiles * profiles);
182
_profiles_event(Profiles * profiles,PhoneEvent * event)183 static int _profiles_event(Profiles * profiles, PhoneEvent * event)
184 {
185 ProfileDefinition * definition = &profiles->profiles[
186 profiles->profiles_cur];
187
188 switch(event->type)
189 {
190 case PHONE_EVENT_TYPE_KEY_TONE:
191 return _event_key_tone(profiles);
192 case PHONE_EVENT_TYPE_OFFLINE:
193 return _event_offline(profiles);
194 case PHONE_EVENT_TYPE_STARTING:
195 return _event_starting(profiles);
196 case PHONE_EVENT_TYPE_STOPPING:
197 return _event_stopping(profiles);
198 case PHONE_EVENT_TYPE_MESSAGE_RECEIVED:
199 _profiles_play(profiles, (definition->sample != NULL)
200 ? definition->sample : "message", 2);
201 break;
202 case PHONE_EVENT_TYPE_MODEM_EVENT:
203 return _event_modem_event(profiles,
204 event->modem_event.event);
205 default: /* not relevant */
206 break;
207 }
208 return 0;
209 }
210
_event_key_tone(Profiles * profiles)211 static int _event_key_tone(Profiles * profiles)
212 {
213 #ifdef DEBUG
214 fprintf(stderr, "DEBUG: %s()\n", __func__);
215 #endif
216 _profiles_play(profiles, "keytone", 1);
217 return 0;
218 }
219
_event_modem_event(Profiles * profiles,ModemEvent * event)220 static int _event_modem_event(Profiles * profiles, ModemEvent * event)
221 {
222 ProfileDefinition * definition = &profiles->profiles[
223 profiles->profiles_cur];
224 ModemCallDirection direction;
225 ModemCallStatus status;
226
227 switch(event->type)
228 {
229 case MODEM_EVENT_TYPE_CALL:
230 if(event->call.call_type != MODEM_CALL_TYPE_VOICE)
231 break;
232 direction = event->call.direction;
233 status = event->call.status;
234 if(direction == MODEM_CALL_DIRECTION_INCOMING
235 && status == MODEM_CALL_STATUS_RINGING)
236 _profiles_play(profiles,
237 (definition->sample != NULL)
238 ? definition->sample
239 : "ringtone", 10);
240 else if(direction == MODEM_CALL_DIRECTION_OUTGOING
241 && status == MODEM_CALL_STATUS_RINGING)
242 _profiles_play(profiles, "ringback", 0);
243 else if(status == MODEM_CALL_STATUS_BUSY)
244 _profiles_play(profiles, "busy", 0);
245 else if(status == MODEM_CALL_STATUS_NONE
246 || status == MODEM_CALL_STATUS_ACTIVE)
247 _profiles_play(profiles, NULL, 0);
248 break;
249 default:
250 break;
251 }
252 return 0;
253 }
254
_event_offline(Profiles * profiles)255 static int _event_offline(Profiles * profiles)
256 {
257 _profiles_set(profiles, PROFILE_TYPE_OFFLINE);
258 return 0;
259 }
260
_event_starting(Profiles * profiles)261 static int _event_starting(Profiles * profiles)
262 {
263 PhonePluginHelper * helper = profiles->helper;
264 ProfileDefinition * definition = &profiles->profiles[
265 profiles->profiles_cur];
266
267 if(definition->online)
268 return 0;
269 if(helper->confirm(helper->phone, "You are currently offline.\n"
270 "Do you want to go online?") != 0)
271 return 1;
272 _profiles_apply(profiles, 0);
273 return 0;
274 }
275
_event_stopping(Profiles * profiles)276 static int _event_stopping(Profiles * profiles)
277 {
278 ProfileDefinition * definition = &profiles->profiles[
279 profiles->profiles_cur];
280
281 /* prevent stopping the modem except if we're going offline */
282 return definition->online ? 1 : 0;
283 }
284
285
286 /* profiles_settings */
287 static gboolean _on_settings_closex(gpointer data);
288 static void _on_settings_cancel(gpointer data);
289 static void _on_settings_changed(gpointer data);
290 static void _on_settings_ok(gpointer data);
291
_profiles_settings(Profiles * profiles)292 static void _profiles_settings(Profiles * profiles)
293 {
294 GtkWidget * vbox;
295 GtkWidget * frame;
296 GtkWidget * bbox;
297 GtkWidget * widget;
298 size_t i;
299
300 #ifdef DEBUG
301 fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__,
302 profiles->profiles[profiles->profiles_cur].name);
303 #endif
304 if(profiles->pr_window != NULL)
305 {
306 gtk_window_present(GTK_WINDOW(profiles->pr_window));
307 return;
308 }
309 profiles->pr_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
310 gtk_container_set_border_width(GTK_CONTAINER(profiles->pr_window), 4);
311 gtk_window_set_default_size(GTK_WINDOW(profiles->pr_window), 200, 300);
312 gtk_window_set_title(GTK_WINDOW(profiles->pr_window), "Profiles");
313 g_signal_connect_swapped(profiles->pr_window, "delete-event",
314 G_CALLBACK(_on_settings_closex), profiles);
315 #if GTK_CHECK_VERSION(3, 0, 0)
316 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
317 #else
318 vbox = gtk_vbox_new(FALSE, 0);
319 #endif
320 /* combo */
321 #if GTK_CHECK_VERSION(3, 0, 0)
322 profiles->pr_combo = gtk_combo_box_text_new();
323 for(i = 0; i < profiles->profiles_cnt; i++)
324 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(
325 profiles->pr_combo), NULL,
326 profiles->profiles[i].name);
327 #else
328 profiles->pr_combo = gtk_combo_box_new_text();
329 for(i = 0; i < profiles->profiles_cnt; i++)
330 gtk_combo_box_append_text(GTK_COMBO_BOX(profiles->pr_combo),
331 profiles->profiles[i].name);
332 #endif
333 g_signal_connect_swapped(profiles->pr_combo, "changed", G_CALLBACK(
334 _on_settings_changed), profiles);
335 gtk_box_pack_start(GTK_BOX(vbox), profiles->pr_combo, FALSE, TRUE, 0);
336 /* frame */
337 frame = gtk_frame_new("Overview");
338 #if GTK_CHECK_VERSION(3, 0, 0)
339 widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
340 #else
341 widget = gtk_vbox_new(FALSE, 4);
342 #endif
343 gtk_container_set_border_width(GTK_CONTAINER(widget), 4);
344 profiles->pr_online = gtk_check_button_new_with_label("Online");
345 gtk_widget_set_sensitive(profiles->pr_online, FALSE);
346 gtk_box_pack_start(GTK_BOX(widget), profiles->pr_online, FALSE, TRUE,
347 0);
348 #if GTK_CHECK_VERSION(3, 0, 0)
349 bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
350 #else
351 bbox = gtk_hbox_new(FALSE, 4);
352 #endif
353 profiles->pr_volume = gtk_label_new("Volume: ");
354 gtk_widget_set_sensitive(profiles->pr_volume, FALSE);
355 gtk_box_pack_start(GTK_BOX(bbox), profiles->pr_volume, FALSE, TRUE, 0);
356 profiles->pr_volume = gtk_progress_bar_new();
357 gtk_widget_set_sensitive(profiles->pr_volume, FALSE);
358 gtk_box_pack_start(GTK_BOX(bbox), profiles->pr_volume, TRUE, TRUE, 0);
359 gtk_box_pack_start(GTK_BOX(widget), bbox, FALSE, TRUE, 0);
360 profiles->pr_vibrator = gtk_check_button_new_with_label("Vibrate");
361 gtk_widget_set_sensitive(profiles->pr_vibrator, FALSE);
362 gtk_box_pack_start(GTK_BOX(widget), profiles->pr_vibrator, FALSE, TRUE,
363 0);
364 gtk_container_add(GTK_CONTAINER(frame), widget);
365 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
366 /* dialog */
367 #if GTK_CHECK_VERSION(3, 0, 0)
368 bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
369 #else
370 bbox = gtk_hbutton_box_new();
371 #endif
372 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
373 gtk_box_set_spacing(GTK_BOX(bbox), 4);
374 widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
375 g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
376 _on_settings_cancel), profiles);
377 gtk_container_add(GTK_CONTAINER(bbox), widget);
378 widget = gtk_button_new_from_stock(GTK_STOCK_OK);
379 g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
380 _on_settings_ok), profiles);
381 gtk_container_add(GTK_CONTAINER(bbox), widget);
382 gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
383 gtk_container_add(GTK_CONTAINER(profiles->pr_window), vbox);
384 gtk_widget_show_all(vbox);
385 _on_settings_cancel(profiles);
386 gtk_window_present(GTK_WINDOW(profiles->pr_window));
387 }
388
_on_settings_closex(gpointer data)389 static gboolean _on_settings_closex(gpointer data)
390 {
391 Profiles * profiles = data;
392
393 _on_settings_cancel(profiles);
394 return TRUE;
395 }
396
_on_settings_cancel(gpointer data)397 static void _on_settings_cancel(gpointer data)
398 {
399 Profiles * profiles = data;
400
401 gtk_widget_hide(profiles->pr_window);
402 gtk_combo_box_set_active(GTK_COMBO_BOX(profiles->pr_combo),
403 profiles->profiles_cur);
404 }
405
_on_settings_changed(gpointer data)406 static void _on_settings_changed(gpointer data)
407 {
408 Profiles * profiles = data;
409 int i;
410 char buf[16];
411 double fraction;
412
413 i = gtk_combo_box_get_active(GTK_COMBO_BOX(profiles->pr_combo));
414 if(i < 0)
415 return;
416 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_online),
417 _profiles_definitions[i].online);
418 fraction = _profiles_definitions[i].volume;
419 if(_profiles_definitions[i].volume > 0)
420 snprintf(buf, sizeof(buf), "%u %%",
421 _profiles_definitions[i].volume);
422 else if(_profiles_definitions[i].volume == 0)
423 snprintf(buf, sizeof(buf), "%s", "Silent");
424 else
425 {
426 snprintf(buf, sizeof(buf), "%s", "Ascending");
427 fraction = 0.0;
428 }
429 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(profiles->pr_volume),
430 fraction / 100.0);
431 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(profiles->pr_volume), buf);
432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_vibrator),
433 _profiles_definitions[i].vibrate);
434 }
435
_on_settings_ok(gpointer data)436 static void _on_settings_ok(gpointer data)
437 {
438 Profiles * profiles = data;
439 ProfileType type;
440
441 gtk_widget_hide(profiles->pr_window);
442 type = gtk_combo_box_get_active(GTK_COMBO_BOX(profiles->pr_combo));
443 _profiles_switch(profiles, type);
444 }
445
446
447 /* accessors */
448 /* profiles_set */
_profiles_set(Profiles * profiles,ProfileType type)449 static int _profiles_set(Profiles * profiles, ProfileType type)
450 {
451 PhonePluginHelper * helper = profiles->helper;
452
453 if(type > profiles->profiles_cnt)
454 return -helper->error(NULL, "Invalid profile", 1);
455 profiles->profiles_cur = type;
456 helper->config_set(helper->phone, "profiles", "default",
457 profiles->profiles[profiles->profiles_cur].name);
458 switch(type)
459 {
460 case PROFILE_TYPE_OFFLINE:
461 helper->config_set(helper->phone, NULL, "online", "0");
462 break;
463 default:
464 helper->config_set(helper->phone, NULL, "online", NULL);
465 break;
466 }
467 return 0;
468 }
469
470
471 /* useful */
472 /* profiles_apply */
_profiles_apply(Profiles * profiles,ProfileType type)473 static void _profiles_apply(Profiles * profiles, ProfileType type)
474 {
475 _profiles_set(profiles, type);
476 }
477
478
479 /* profiles_play */
_profiles_play(Profiles * profiles,char const * sample,int vibrator)480 static void _profiles_play(Profiles * profiles, char const * sample,
481 int vibrator)
482 {
483 PhonePluginHelper * helper = profiles->helper;
484 ProfileDefinition * definition = &profiles->profiles[
485 profiles->profiles_cur];
486 PhoneEvent event;
487
488 #ifdef DEBUG
489 fprintf(stderr, "DEBUG: %s(\"%s\") %s\n", __func__, sample,
490 definition->name);
491 #endif
492 if(sample == NULL)
493 {
494 /* cancel the current sample */
495 memset(&event, 0, sizeof(event));
496 event.type = PHONE_EVENT_TYPE_AUDIO_STOP;
497 helper->event(helper->phone, &event);
498 }
499 else if(definition->volume != PROFILE_VOLUME_SILENT)
500 {
501 memset(&event, 0, sizeof(event));
502 event.type = PHONE_EVENT_TYPE_AUDIO_PLAY;
503 event.audio_play.sample = sample;
504 helper->event(helper->phone, &event);
505 }
506 profiles->vibrator = max(profiles->vibrator, vibrator);
507 if(vibrator == 0)
508 {
509 /* stop the vibrator */
510 memset(&event, 0, sizeof(event));
511 event.type = PHONE_EVENT_TYPE_VIBRATOR_OFF;
512 helper->event(helper->phone, &event);
513 /* remove the callback */
514 if(profiles->source != 0)
515 g_source_remove(profiles->source);
516 profiles->source = 0;
517 profiles->vibrator = 0;
518 }
519 else if(definition->vibrate && profiles->vibrator != 0)
520 {
521 memset(&event, 0, sizeof(event));
522 event.type = PHONE_EVENT_TYPE_VIBRATOR_ON;
523 helper->event(helper->phone, &event);
524 if(profiles->source != 0)
525 g_source_remove(profiles->source);
526 profiles->source = g_timeout_add(PROFILE_VIBRATOR_LOOP,
527 _profiles_on_vibrate, profiles);
528 }
529 }
530
531
532 /* profiles_switch */
_profiles_switch(Profiles * profiles,ProfileType type)533 static void _profiles_switch(Profiles * profiles, ProfileType type)
534 {
535 PhonePluginHelper * helper = profiles->helper;
536 ProfileType current = profiles->profiles_cur;
537 PhoneEvent pevent;
538
539 if(type == current)
540 return;
541 if(type > profiles->profiles_cnt)
542 /* XXX report error */
543 return;
544 _profiles_apply(profiles, type);
545 memset(&pevent, 0, sizeof(pevent));
546 if(profiles->profiles[current].online
547 && !profiles->profiles[type].online)
548 {
549 /* go offline */
550 pevent.type = PHONE_EVENT_TYPE_STOPPING;
551 helper->event(helper->phone, &pevent);
552 }
553 else if(!profiles->profiles[current].online
554 && profiles->profiles[type].online)
555 {
556 /* go online */
557 pevent.type = PHONE_EVENT_TYPE_STARTING;
558 helper->event(helper->phone, &pevent);
559 }
560 }
561
562
563 /* callbacks */
564 /* profiles_on_vibrate */
_profiles_on_vibrate(gpointer data)565 static gboolean _profiles_on_vibrate(gpointer data)
566 {
567 Profiles * profiles = data;
568 PhonePluginHelper * helper = profiles->helper;
569 PhoneEvent event;
570
571 memset(&event, 0, sizeof(event));
572 if(profiles->vibrator < 0)
573 {
574 /* stop the vibrator */
575 event.type = PHONE_EVENT_TYPE_VIBRATOR_OFF;
576 helper->event(helper->phone, &event);
577 /* vibrate again only if necessary */
578 profiles->vibrator = (-profiles->vibrator) - 1;
579 }
580 else if(profiles->vibrator > 0)
581 {
582 /* start the vibrator */
583 event.type = PHONE_EVENT_TYPE_VIBRATOR_ON;
584 helper->event(helper->phone, &event);
585 /* pause the vibrator next time */
586 profiles->vibrator = -profiles->vibrator;
587 }
588 else
589 {
590 profiles->source = 0;
591 return FALSE;
592 }
593 return TRUE;
594 }
595