Phone
/* $Id$ */
							/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Desktop Phone */
							/* Redistribution and use in source and binary forms, with or without
							 * modification, are permitted provided that the following conditions are
							 * met:
							 *
							 * 1. Redistributions of source code must retain the above copyright notice,
							 *    this list of conditions and the following disclaimer.
							 *
							 * 2. Redistributions in binary form must reproduce the above copyright notice,
							 *    this list of conditions and the following disclaimer in the documentation
							 *    and/or other materials provided with the distribution.
							 *
							 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
							 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
							 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
							 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
							 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
							 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
							 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
							 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
							 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
							 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
							#include <stdio.h>
							#include <string.h>
							#include <errno.h>
							#include <libintl.h>
							#include <gtk/gtk.h>
							#include <System.h>
							#include "Phone.h"
							#define _(string) gettext(string)
							#define N_(string) string
							/* macros */
							#define max(a, b) ((a) > (b) ? (a) : (b))
							/* Profiles */
							/* private */
							/* types */
							typedef enum _ProfileColumn
							{
								PROFILE_COLUMN_RADIO = 0,
								PROFILE_COLUMN_TYPE,
								PROFILE_COLUMN_DEFAULT,
								PROFILE_COLUMN_ONLINE,
								PROFILE_COLUMN_VOLUME,
								PROFILE_COLUMN_VIBRATE,
								PROFILE_COLUMN_SAMPLE,
								PROFILE_COLUMN_ICON,
								PROFILE_COLUMN_NAME,
								PROFILE_COLUMN_NAME_DISPLAY
							} ProfileColumn;
							#define PROFILE_COLUMN_LAST PROFILE_COLUMN_NAME_DISPLAY
							#define PROFILE_COLUMN_COUNT (PROFILE_COLUMN_LAST + 1)
							typedef enum _ProfileType
							{
								PROFILE_TYPE_GENERAL = 0,
								PROFILE_TYPE_BEEP,
								PROFILE_TYPE_SILENT,
								PROFILE_TYPE_OFFLINE
							} ProfileType;
							#define PROFILE_TYPE_LAST PROFILE_TYPE_OFFLINE
							#define PROFILE_TYPE_COUNT (PROFILE_TYPE_LAST + 1)
							typedef enum _ProfileVolume
							{
								PROFILE_VOLUME_SILENT	= 0,
								PROFILE_VOLUME_25	= 25,
								PROFILE_VOLUME_50	= 50,
								PROFILE_VOLUME_75	= 75,
								PROFILE_VOLUME_100	= 100,
								PROFILE_VOLUME_ASC	= -1
							} ProfileVolume;
							typedef struct _ProfileDefinition
							{
								char const * icon;
								char const * name;
								gboolean online;
								ProfileVolume volume;
								gboolean vibrate;
								char const * sample;
							} ProfileDefinition;
							typedef struct _PhonePlugin
							{
								PhonePluginHelper * helper;
								guint source;
								/* profiles */
								ProfileDefinition * profiles;
								size_t profiles_cnt;
								size_t profiles_cur;
								/* vibrator */
								int vibrator;
								/* settings */
								GtkWidget * pr_window;
								GtkListStore * pr_store;
								GtkWidget * pr_view;
								GtkWidget * pr_online;
								GtkWidget * pr_volume;
								GtkWidget * pr_ring;
								GtkWidget * pr_vibrator;
							} Profiles;
							/* constants */
							#define PROFILE_VIBRATOR_LOOP	500
							/* variables */
							static ProfileDefinition _profiles_definitions[PROFILE_TYPE_COUNT] =
							{
								{ NULL, N_("General"),	TRUE,	PROFILE_VOLUME_ASC,	TRUE,	NULL	},
								{ NULL, N_("Beep"),	TRUE,	PROFILE_VOLUME_25,	TRUE,	"beep"	},
								{ "audio-volume-muted",
									N_("Silent"),	TRUE,	PROFILE_VOLUME_SILENT,	TRUE,	NULL	},
								{ NULL, N_("Offline"),	FALSE,	PROFILE_VOLUME_SILENT,	FALSE,	NULL	}
							};
							/* prototypes */
							/* plug-in */
							static Profiles * _profiles_init(PhonePluginHelper * helper);
							static void _profiles_destroy(Profiles * profiles);
							static int _profiles_event(Profiles * profiles, PhoneEvent * event);
							static void _profiles_settings(Profiles * profiles);
							/* accessors */
							static int _profiles_set(Profiles * profiles, ProfileType type);
							/* useful */
							static int _profiles_load(Profiles * profiles);
							static int _profiles_save(Profiles * profiles);
							static void _profiles_play(Profiles * profiles, char const * sound,
									int vibrator);
							static void _profiles_switch(Profiles * profiles, ProfileType type);
							/* callbacks */
							static gboolean _profiles_on_vibrate(gpointer data);
							/* public */
							/* variables */
							PhonePluginDefinition plugin =
							{
								"Profiles",
								"system-config-users",
								NULL,
								_profiles_init,
								_profiles_destroy,
								_profiles_event,
								_profiles_settings
							};
							/* private */
							/* functions */
							/* profiles_init */
							static Profiles * _profiles_init(PhonePluginHelper * helper)
							{
								Profiles * profiles;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								if((profiles = object_new(sizeof(*profiles))) == NULL)
									return NULL;
								profiles->helper = helper;
								profiles->source = 0;
								profiles->profiles = _profiles_definitions;
								profiles->profiles_cnt = sizeof(_profiles_definitions)
									/ sizeof(*_profiles_definitions);
								profiles->profiles_cur = PROFILE_TYPE_GENERAL;
								profiles->vibrator = 0;
								profiles->pr_window = NULL;
								profiles->pr_store = gtk_list_store_new(PROFILE_COLUMN_COUNT,
										G_TYPE_BOOLEAN,		/* radio */
										G_TYPE_UINT,		/* type */
										G_TYPE_BOOLEAN,		/* default */
										G_TYPE_BOOLEAN,		/* online */
										G_TYPE_INT,		/* volume */
										G_TYPE_BOOLEAN,		/* vibrate */
										G_TYPE_STRING,		/* sample */
										GDK_TYPE_PIXBUF,	/* icon */
										G_TYPE_STRING,		/* name */
										G_TYPE_STRING);		/* name display */
								_profiles_load(profiles);
								return profiles;
							}
							/* profiles_destroy */
							static void _profiles_destroy(Profiles * profiles)
							{
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								if(profiles->source != 0)
									g_source_remove(profiles->source);
								if(profiles->pr_window != NULL)
									gtk_widget_destroy(profiles->pr_window);
								object_delete(profiles);
							}
							/* profiles_event */
							static int _event_key_tone(Profiles * profiles);
							static int _event_modem_event(Profiles * profiles, ModemEvent * event);
							static int _event_offline(Profiles * profiles);
							static int _event_starting(Profiles * profiles);
							static int _event_stopping(Profiles * profiles);
							static int _profiles_event(Profiles * profiles, PhoneEvent * event)
							{
								ProfileDefinition * profile = &profiles->profiles[
									profiles->profiles_cur];
								switch(event->type)
								{
									case PHONE_EVENT_TYPE_KEY_TONE:
										return _event_key_tone(profiles);
									case PHONE_EVENT_TYPE_OFFLINE:
										return _event_offline(profiles);
									case PHONE_EVENT_TYPE_STARTING:
										return _event_starting(profiles);
									case PHONE_EVENT_TYPE_STOPPING:
										return _event_stopping(profiles);
									case PHONE_EVENT_TYPE_MESSAGE_RECEIVED:
										_profiles_play(profiles, (profile->sample != NULL)
												? profile->sample : "message", 2);
										break;
									case PHONE_EVENT_TYPE_MODEM_EVENT:
										return _event_modem_event(profiles,
												event->modem_event.event);
									default: /* not relevant */
										break;
								}
								return 0;
							}
							static int _event_key_tone(Profiles * profiles)
							{
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s()\n", __func__);
							#endif
								_profiles_play(profiles, "keytone", 1);
								return 0;
							}
							static int _event_modem_event(Profiles * profiles, ModemEvent * event)
							{
								ProfileDefinition * profile = &profiles->profiles[
									profiles->profiles_cur];
								ModemCallDirection direction;
								ModemCallStatus status;
								switch(event->type)
								{
									case MODEM_EVENT_TYPE_CALL:
										if(event->call.call_type != MODEM_CALL_TYPE_VOICE)
											break;
										direction = event->call.direction;
										status = event->call.status;
										if(direction == MODEM_CALL_DIRECTION_INCOMING
												&& status == MODEM_CALL_STATUS_RINGING)
											_profiles_play(profiles,
													(profile->sample != NULL)
													? profile->sample : "ringtone",
													10);
										else if(direction == MODEM_CALL_DIRECTION_OUTGOING
												&& status == MODEM_CALL_STATUS_RINGING)
											_profiles_play(profiles, "ringback", 0);
										else if(status == MODEM_CALL_STATUS_BUSY)
											_profiles_play(profiles, "busy", 0);
										else if(status == MODEM_CALL_STATUS_NONE
												|| status == MODEM_CALL_STATUS_ACTIVE)
											_profiles_play(profiles, NULL, 0);
										break;
									default:
										break;
								}
								return 0;
							}
							static int _event_offline(Profiles * profiles)
							{
								_profiles_set(profiles, PROFILE_TYPE_OFFLINE);
								return 0;
							}
							static int _event_starting(Profiles * profiles)
							{
								PhonePluginHelper * helper = profiles->helper;
								ProfileDefinition * profile = &profiles->profiles[
									profiles->profiles_cur];
								if(profile->online)
									return 0;
								if(helper->confirm(helper->phone, "You are currently offline.\n"
											"Do you want to go online?") != 0)
									return 1;
								_profiles_switch(profiles, PROFILE_TYPE_GENERAL);
								return 0;
							}
							static int _event_stopping(Profiles * profiles)
							{
								ProfileDefinition * profile = &profiles->profiles[
									profiles->profiles_cur];
								/* prevent stopping the modem except if we're going offline */
								return profile->online ? 1 : 0;
							}
							/* profiles_settings */
							static void _on_settings_activated(GtkTreeView * view, GtkTreePath * path,
									GtkTreeViewColumn * column, gpointer data);
							static gboolean _on_settings_closex(gpointer data);
							static void _on_settings_cancel(gpointer data);
							static void _on_settings_changed(GtkTreeSelection * treesel, gpointer data);
							static void _on_settings_ok(gpointer data);
							static void _on_settings_toggled(GtkCellRendererToggle * renderer,
									char * path, gpointer data);
							static void _profiles_settings(Profiles * profiles)
							{
								GtkWidget * vbox;
								GtkWidget * frame;
								GtkWidget * bbox;
								GtkWidget * widget;
								GtkTreeSelection * treesel;
								GtkTreeViewColumn * column;
								GtkCellRenderer * renderer;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__,
										profiles->profiles[profiles->profiles_cur].name);
							#endif
								if(profiles->pr_window != NULL)
								{
									gtk_window_present(GTK_WINDOW(profiles->pr_window));
									return;
								}
								profiles->pr_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
								gtk_container_set_border_width(GTK_CONTAINER(profiles->pr_window), 4);
								gtk_window_set_default_size(GTK_WINDOW(profiles->pr_window), 200, 300);
								gtk_window_set_title(GTK_WINDOW(profiles->pr_window), "Profiles");
								g_signal_connect_swapped(profiles->pr_window, "delete-event",
										G_CALLBACK(_on_settings_closex), profiles);
							#if GTK_CHECK_VERSION(3, 0, 0)
								vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
							#else
								vbox = gtk_vbox_new(FALSE, 0);
							#endif
								/* view */
								widget = gtk_scrolled_window_new(NULL, NULL);
								gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
										GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
								profiles->pr_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
											profiles->pr_store));
								gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(profiles->pr_view),
										FALSE);
								gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(profiles->pr_view), TRUE);
								g_signal_connect(profiles->pr_view, "row-activated",
										G_CALLBACK(_on_settings_activated), profiles);
								treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profiles->pr_view));
								g_signal_connect(treesel, "changed", G_CALLBACK(_on_settings_changed),
										profiles);
								/* view: radio */
								renderer = gtk_cell_renderer_toggle_new();
								g_signal_connect(renderer, "toggled", G_CALLBACK(
											_on_settings_toggled), profiles);
								column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
										"active", PROFILE_COLUMN_DEFAULT,
										"radio", PROFILE_COLUMN_RADIO, NULL);
								gtk_tree_view_append_column(GTK_TREE_VIEW(profiles->pr_view), column);
								/* view: icon */
								renderer = gtk_cell_renderer_pixbuf_new();
								column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
										"pixbuf", PROFILE_COLUMN_ICON, NULL);
								gtk_tree_view_append_column(GTK_TREE_VIEW(profiles->pr_view), column);
								/* view: name */
								renderer = gtk_cell_renderer_text_new();
								column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
										"text", PROFILE_COLUMN_NAME_DISPLAY, NULL);
								gtk_tree_view_append_column(GTK_TREE_VIEW(profiles->pr_view), column);
								gtk_container_add(GTK_CONTAINER(widget), profiles->pr_view);
								gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
								/* frame */
								frame = gtk_frame_new("Overview");
							#if GTK_CHECK_VERSION(3, 0, 0)
								widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
							#else
								widget = gtk_vbox_new(FALSE, 4);
							#endif
								gtk_container_set_border_width(GTK_CONTAINER(widget), 4);
								profiles->pr_online = gtk_check_button_new_with_label(_("Online"));
								gtk_widget_set_sensitive(profiles->pr_online, FALSE);
								gtk_box_pack_start(GTK_BOX(widget), profiles->pr_online, FALSE, TRUE,
										0);
							#if GTK_CHECK_VERSION(3, 0, 0)
								bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
							#else
								bbox = gtk_hbox_new(FALSE, 4);
							#endif
								profiles->pr_volume = gtk_label_new(_("Volume: "));
								gtk_widget_set_sensitive(profiles->pr_volume, FALSE);
								gtk_box_pack_start(GTK_BOX(bbox), profiles->pr_volume, FALSE, TRUE, 0);
								profiles->pr_volume = gtk_progress_bar_new();
							#if GTK_CHECK_VERSION(3, 0, 0)
								gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(profiles->pr_volume),
										TRUE);
							#endif
								gtk_progress_bar_set_text(GTK_PROGRESS_BAR(profiles->pr_volume), "");
								gtk_widget_set_sensitive(profiles->pr_volume, FALSE);
								gtk_box_pack_start(GTK_BOX(bbox), profiles->pr_volume, TRUE, TRUE, 0);
								gtk_box_pack_start(GTK_BOX(widget), bbox, FALSE, TRUE, 0);
								profiles->pr_ring = gtk_check_button_new_with_label(_("Ring"));
								gtk_widget_set_sensitive(profiles->pr_ring, FALSE);
								gtk_box_pack_start(GTK_BOX(widget), profiles->pr_ring, FALSE, TRUE,
										0);
								profiles->pr_vibrator = gtk_check_button_new_with_label(_("Vibrate"));
								gtk_widget_set_sensitive(profiles->pr_vibrator, FALSE);
								gtk_box_pack_start(GTK_BOX(widget), profiles->pr_vibrator, FALSE, TRUE,
										0);
								gtk_container_add(GTK_CONTAINER(frame), widget);
								gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
								/* dialog */
							#if GTK_CHECK_VERSION(3, 0, 0)
								bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
							#else
								bbox = gtk_hbutton_box_new();
							#endif
								gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
								gtk_box_set_spacing(GTK_BOX(bbox), 4);
								widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
								g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
											_on_settings_cancel), profiles);
								gtk_container_add(GTK_CONTAINER(bbox), widget);
								widget = gtk_button_new_from_stock(GTK_STOCK_OK);
								g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
											_on_settings_ok), profiles);
								gtk_container_add(GTK_CONTAINER(bbox), widget);
								gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
								gtk_container_add(GTK_CONTAINER(profiles->pr_window), vbox);
								gtk_widget_show_all(vbox);
								_on_settings_cancel(profiles);
								gtk_window_present(GTK_WINDOW(profiles->pr_window));
							}
							static void _on_settings_activated(GtkTreeView * view, GtkTreePath * path,
									GtkTreeViewColumn * column, gpointer data)
							{
								Profiles * profiles = data;
								GtkTreeModel * model = GTK_TREE_MODEL(profiles->pr_store);
								GtkTreeIter iter;
								ProfileType type;
								(void) view;
								(void) column;
								if(gtk_tree_model_get_iter(model, &iter, path) != TRUE)
									return;
								gtk_tree_model_get(model, &iter, PROFILE_COLUMN_TYPE, &type, -1);
								_profiles_set(profiles, type);
							}
							static gboolean _on_settings_closex(gpointer data)
							{
								Profiles * profiles = data;
								_on_settings_cancel(profiles);
								return TRUE;
							}
							static void _on_settings_cancel(gpointer data)
							{
								Profiles * profiles = data;
								GtkTreeModel * model = GTK_TREE_MODEL(profiles->pr_store);
								GtkTreeIter iter;
								gboolean valid;
								size_t i;
								gtk_widget_hide(profiles->pr_window);
								valid = gtk_tree_model_get_iter_first(model, &iter);
								for(i = 0; valid; valid = gtk_tree_model_iter_next(model, &iter), i++)
									gtk_list_store_set(profiles->pr_store, &iter,
											PROFILE_COLUMN_DEFAULT,
											(i == profiles->profiles_cur) ? TRUE : FALSE,
											-1);
							}
							static void _on_settings_changed(GtkTreeSelection * treesel, gpointer data)
							{
								Profiles * profiles = data;
								GtkTreeModel * model;
								GtkTreeIter iter;
								gboolean online;
								ProfileVolume volume;
								gboolean ring;
								gboolean vibrate;
								char buf[16];
								double fraction;
								if(gtk_tree_selection_get_selected(treesel, &model, &iter) != TRUE)
									return;
								gtk_tree_model_get(model, &iter, PROFILE_COLUMN_ONLINE, &online,
										PROFILE_COLUMN_VOLUME, &volume,
										PROFILE_COLUMN_VIBRATE, &vibrate, -1);
								fraction = volume;
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_online),
										online);
								if(volume > 0)
								{
									snprintf(buf, sizeof(buf), "%u %%", volume);
									ring = TRUE;
								}
								else if(volume == 0)
								{
									snprintf(buf, sizeof(buf), "%s", _("Silent"));
									ring = FALSE;
								}
								else
								{
									snprintf(buf, sizeof(buf), "%s", _("Ascending"));
									fraction = 0.0;
									ring = TRUE;
								}
								gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(profiles->pr_volume),
										fraction / 100.0);
								gtk_progress_bar_set_text(GTK_PROGRESS_BAR(profiles->pr_volume), buf);
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_ring),
										ring);
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_vibrator),
										vibrate);
							}
							static void _on_settings_ok(gpointer data)
							{
								Profiles * profiles = data;
								GtkTreeModel * model = GTK_TREE_MODEL(profiles->pr_store);
								GtkTreeIter iter;
								ProfileType type;
								gboolean valid;
								gtk_widget_hide(profiles->pr_window);
								for(valid = gtk_tree_model_get_iter_first(model, &iter); valid;
										valid = gtk_tree_model_iter_next(model, &iter))
								{
									gtk_tree_model_get(model, &iter,
											PROFILE_COLUMN_TYPE, &type,
											PROFILE_COLUMN_DEFAULT, &valid, -1);
									if(valid)
									{
										_profiles_switch(profiles, type);
										break;
									}
								}
							}
							static void _on_settings_toggled(GtkCellRendererToggle * renderer,
									char * path, gpointer data)
							{
								Profiles * profiles = data;
								GtkTreeModel * model = GTK_TREE_MODEL(profiles->pr_store);
								GtkTreeIter iter;
								ProfileType type;
								(void) renderer;
								gtk_tree_model_get_iter_from_string(model, &iter, path);
								gtk_tree_model_get(model, &iter, PROFILE_COLUMN_TYPE, &type, -1);
								_profiles_set(profiles, type);
							}
							/* accessors */
							/* profiles_set */
							static int _profiles_set(Profiles * profiles, ProfileType type)
							{
								PhonePluginHelper * helper = profiles->helper;
								GtkTreeModel * model = GTK_TREE_MODEL(profiles->pr_store);
								GtkTreeIter iter;
								gboolean valid;
								ProfileType pt;
								if(type >= profiles->profiles_cnt)
									return -helper->error(NULL, _("Invalid profile"), 1);
								profiles->profiles_cur = type;
								for(valid = gtk_tree_model_get_iter_first(model, &iter); valid;
										valid = gtk_tree_model_iter_next(model, &iter))
								{
									gtk_tree_model_get(model, &iter, PROFILE_COLUMN_TYPE, &pt, -1);
									gtk_list_store_set(profiles->pr_store, &iter,
											PROFILE_COLUMN_DEFAULT,
											(pt == type) ? TRUE : FALSE, -1);
								}
								return 0;
							}
							/* useful */
							/* profiles_load */
							static int _profiles_load(Profiles * profiles)
							{
								PhonePluginHelper * helper = profiles->helper;
								GtkIconTheme * theme;
								ProfileDefinition * profile;
								GtkTreeIter iter;
								char const * p;
								size_t i = 0;
								theme = gtk_icon_theme_get_default();
								/* profiles */
								if((p = helper->config_get(helper->phone, "profiles", "default"))
										== NULL)
									p = profiles->profiles[0].name;
								gtk_list_store_clear(profiles->pr_store);
								for(i = 0; i < profiles->profiles_cnt; i++)
								{
									profile = &profiles->profiles[i];
									gtk_list_store_append(profiles->pr_store, &iter);
									gtk_list_store_set(profiles->pr_store, &iter,
											PROFILE_COLUMN_RADIO, TRUE,
											PROFILE_COLUMN_TYPE, i,
											PROFILE_COLUMN_DEFAULT,
											(strcmp(profile->name, p) == 0) ? TRUE : FALSE,
											PROFILE_COLUMN_ONLINE, profile->online,
											PROFILE_COLUMN_VOLUME, profile->volume,
											PROFILE_COLUMN_VIBRATE, profile->vibrate,
											PROFILE_COLUMN_SAMPLE, profile->sample,
											PROFILE_COLUMN_ICON,
											gtk_icon_theme_load_icon(theme,
											(profile->icon != NULL)
											? profile->icon : "gnome-settings",
											16, 0, NULL),
											PROFILE_COLUMN_NAME, profile->name,
											PROFILE_COLUMN_NAME_DISPLAY, _(profile->name),
											-1);
								}
								if(i == profiles->profiles_cnt)
									i = PROFILE_TYPE_GENERAL;
								return _profiles_set(profiles, i);
							}
							/* profiles_play */
							static void _profiles_play(Profiles * profiles, char const * sample,
									int vibrator)
							{
								PhonePluginHelper * helper = profiles->helper;
								ProfileDefinition * profile = &profiles->profiles[
									profiles->profiles_cur];
								PhoneEvent event;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\") %s\n", __func__, sample,
										profile->name);
							#endif
								if(sample == NULL)
								{
									/* cancel the current sample */
									memset(&event, 0, sizeof(event));
									event.type = PHONE_EVENT_TYPE_AUDIO_STOP;
									helper->event(helper->phone, &event);
								}
								else if(profile->volume != PROFILE_VOLUME_SILENT)
								{
									memset(&event, 0, sizeof(event));
									event.type = PHONE_EVENT_TYPE_AUDIO_PLAY;
									event.audio_play.sample = sample;
									helper->event(helper->phone, &event);
								}
								profiles->vibrator = max(profiles->vibrator, vibrator);
								if(vibrator == 0)
								{
									/* stop the vibrator */
									memset(&event, 0, sizeof(event));
									event.type = PHONE_EVENT_TYPE_VIBRATOR_OFF;
									helper->event(helper->phone, &event);
									/* remove the callback */
									if(profiles->source != 0)
										g_source_remove(profiles->source);
									profiles->source = 0;
									profiles->vibrator = 0;
								}
								else if(profile->vibrate && profiles->vibrator != 0)
								{
									memset(&event, 0, sizeof(event));
									event.type = PHONE_EVENT_TYPE_VIBRATOR_ON;
									helper->event(helper->phone, &event);
									if(profiles->source != 0)
										g_source_remove(profiles->source);
									profiles->source = g_timeout_add(PROFILE_VIBRATOR_LOOP,
											_profiles_on_vibrate, profiles);
								}
							}
							/* profiles_save */
							static int _profiles_save(Profiles * profiles)
							{
								int ret = 0;
								PhonePluginHelper * helper = profiles->helper;
								/* default profile */
								ret |= helper->config_set(helper->phone, "profiles", "default",
										profiles->profiles[profiles->profiles_cur].name);
								/* online */
								ret |= helper->config_set(helper->phone, NULL, "online",
										profiles->profiles[profiles->profiles_cur].online
										? NULL : "0");
								return ret;
							}
							/* profiles_switch */
							static void _profiles_switch(Profiles * profiles, ProfileType type)
							{
								PhonePluginHelper * helper = profiles->helper;
								ProfileType current = profiles->profiles_cur;
								PhoneEvent pevent;
								if(type == current)
									return;
								if(_profiles_set(profiles, type) != 0)
									return;
								_profiles_save(profiles);
								memset(&pevent, 0, sizeof(pevent));
								if(profiles->profiles[current].online
										&& !profiles->profiles[type].online)
								{
									/* go offline */
									pevent.type = PHONE_EVENT_TYPE_STOPPING;
									helper->event(helper->phone, &pevent);
								}
								else if(!profiles->profiles[current].online
										&& profiles->profiles[type].online)
								{
									/* go online */
									pevent.type = PHONE_EVENT_TYPE_STARTING;
									helper->event(helper->phone, &pevent);
								}
							}
							/* callbacks */
							/* profiles_on_vibrate */
							static gboolean _profiles_on_vibrate(gpointer data)
							{
								Profiles * profiles = data;
								PhonePluginHelper * helper = profiles->helper;
								PhoneEvent event;
								memset(&event, 0, sizeof(event));
								if(profiles->vibrator < 0)
								{
									/* stop the vibrator */
									event.type = PHONE_EVENT_TYPE_VIBRATOR_OFF;
									helper->event(helper->phone, &event);
									/* vibrate again only if necessary */
									profiles->vibrator = (-profiles->vibrator) - 1;
								}
								else if(profiles->vibrator > 0)
								{
									/* start the vibrator */
									event.type = PHONE_EVENT_TYPE_VIBRATOR_ON;
									helper->event(helper->phone, &event);
									/* pause the vibrator next time */
									profiles->vibrator = -profiles->vibrator;
								}
								else
								{
									profiles->source = 0;
									return FALSE;
								}
								return TRUE;
							}
							