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