Desktop

/* $Id: profiles.c,v 1.34 2011/10/31 02:11:52 khorben Exp $ */
/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Phone */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* TODO:
* - move PulseAudio support in a dedicated plug-in */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include <gtk/gtk.h>
#include <pulse/pulseaudio.h>
#include <System.h>
#include "Phone.h"
#include "../../config.h"
#define max(a, b) ((a) > (b) ? (a) : (b))
#ifndef PREFIX
# define PREFIX "/usr/local"
#endif
#ifndef DATADIR
# define DATADIR PREFIX "/share"
#endif
/* Profiles */
/* private */
/* types */
typedef enum _ProfileType
{
PROFILE_TYPE_GENERAL = 0,
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 * name;
gboolean online;
ProfileVolume volume;
gboolean vibrate;
} ProfileDefinition;
typedef struct _Profiles
{
guint source;
/* profiles */
ProfileDefinition * profiles;
size_t profiles_cnt;
size_t profiles_cur;
/* vibrator */
int vibrator;
/* settings */
GtkWidget * pr_window;
GtkWidget * pr_combo;
GtkWidget * pr_online;
GtkWidget * pr_volume;
GtkWidget * pr_vibrator;
/* pulseaudio */
pa_threaded_mainloop * pam;
pa_context * pac;
pa_operation * pao;
} Profiles;
/* variables */
static ProfileDefinition _profiles_definitions[PROFILE_TYPE_COUNT] =
{
{ "General", TRUE, PROFILE_VOLUME_ASC, TRUE },
{ "Silent", TRUE, PROFILE_VOLUME_SILENT, TRUE },
{ "Offline", FALSE, PROFILE_VOLUME_SILENT, FALSE }
};
/* prototypes */
/* plug-in */
static int _profiles_init(PhonePlugin * plugin);
static void _profiles_destroy(PhonePlugin * plugin);
static int _profiles_event(PhonePlugin * plugin, PhoneEvent * event);
static void _profiles_settings(PhonePlugin * plugin);
/* useful */
static void _profiles_apply(PhonePlugin * plugin, ProfileType type);
static void _profiles_play(PhonePlugin * plugin, char const * sound,
int vibrate);
static void _profiles_switch(PhonePlugin * plugin, ProfileType type);
/* callbacks */
static gboolean _profiles_on_vibrate(gpointer data);
/* public */
/* variables */
PhonePlugin plugin =
{
NULL,
"Profiles",
"system-config-users",
_profiles_init,
_profiles_destroy,
_profiles_event,
_profiles_settings,
NULL
};
/* private */
/* functions */
/* profiles_init */
static int _profiles_init(PhonePlugin * plugin)
{
Profiles * profiles;
pa_mainloop_api * mapi = NULL;
char const * p;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if((profiles = object_new(sizeof(*profiles))) == NULL)
return -1;
plugin->priv = profiles;
profiles->source = 0;
profiles->profiles = _profiles_definitions;
profiles->profiles_cnt = sizeof(_profiles_definitions)
/ sizeof(*_profiles_definitions);
profiles->profiles_cur = 0;
if((p = plugin->helper->config_get(plugin->helper->phone, "profiles",
"default")) != NULL)
for(i = 0; i < profiles->profiles_cnt; i++)
if(strcmp(profiles->profiles[i].name, p) == 0)
{
profiles->profiles_cur = i;
break;
}
profiles->vibrator = 0;
profiles->pr_window = NULL;
profiles->pam = pa_threaded_mainloop_new();
profiles->pac = NULL;
profiles->pao = NULL;
if(profiles->pam == NULL)
{
_profiles_destroy(plugin);
return error_set_code(1, "%s",
"Could not initialize PulseAudio");
}
mapi = pa_threaded_mainloop_get_api(profiles->pam);
/* XXX update the context name */
if((profiles->pac = pa_context_new(mapi, PACKAGE)) == NULL)
{
_profiles_destroy(plugin);
return error_set_code(1, "%s",
"Could not initialize PulseAudio");
}
pa_context_connect(profiles->pac, NULL, 0, NULL);
pa_threaded_mainloop_start(profiles->pam);
return 0;
}
/* profiles_destroy */
static void _profiles_destroy(PhonePlugin * plugin)
{
Profiles * profiles = plugin->priv;
#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);
if(profiles->pao != NULL)
pa_operation_cancel(profiles->pao);
if(profiles->pac != NULL)
pa_context_unref(profiles->pac);
pa_threaded_mainloop_free(profiles->pam);
object_delete(profiles);
}
/* profiles_event */
static int _event_key_tone(PhonePlugin * plugin);
static int _event_modem_event(PhonePlugin * plugin, ModemEvent * event);
static int _event_starting(PhonePlugin * plugin);
static int _event_stopping(PhonePlugin * plugin);
static int _profiles_event(PhonePlugin * plugin, PhoneEvent * event)
{
switch(event->type)
{
case PHONE_EVENT_TYPE_KEY_TONE:
return _event_key_tone(plugin);
case PHONE_EVENT_TYPE_STARTING:
return _event_starting(plugin);
case PHONE_EVENT_TYPE_STOPPING:
return _event_stopping(plugin);
case PHONE_EVENT_TYPE_MESSAGE_RECEIVED:
_profiles_play(plugin, "message", 2);
break;
case PHONE_EVENT_TYPE_MODEM_EVENT:
return _event_modem_event(plugin,
event->modem_event.event);
default: /* not relevant */
break;
}
return 0;
}
static int _event_key_tone(PhonePlugin * plugin)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
_profiles_play(plugin, "keytone", 1);
return 0;
}
static int _event_modem_event(PhonePlugin * plugin, ModemEvent * event)
{
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(plugin, "ringtone", 10);
else if(status == MODEM_CALL_STATUS_NONE
|| status == MODEM_CALL_STATUS_ACTIVE)
_profiles_play(plugin, NULL, 0);
break;
default:
break;
}
return 0;
}
static int _event_starting(PhonePlugin * plugin)
{
PhonePluginHelper * helper = plugin->helper;
Profiles * profiles = plugin->priv;
ProfileDefinition * definition = &profiles->profiles[
profiles->profiles_cur];
if(definition->online)
return 0;
if(helper->confirm(helper->phone, "You are currently offline.\n"
"Do you want to go online?") != 0)
return 1;
_profiles_apply(plugin, 0);
return 0;
}
static int _event_stopping(PhonePlugin * plugin)
{
Profiles * profiles = plugin->priv;
ProfileDefinition * definition = &profiles->profiles[
profiles->profiles_cur];
/* prevent stopping the modem except if we're going offline */
return definition->online ? 1 : 0;
}
/* profiles_settings */
static gboolean _on_settings_closex(gpointer data);
static void _on_settings_cancel(gpointer data);
static void _on_settings_changed(gpointer data);
static void _on_settings_ok(gpointer data);
static void _profiles_settings(PhonePlugin * plugin)
{
Profiles * profiles = plugin->priv;
GtkWidget * vbox;
GtkWidget * frame;
GtkWidget * bbox;
GtkWidget * widget;
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, plugin->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(G_OBJECT(profiles->pr_window), "delete-event",
G_CALLBACK(_on_settings_closex), plugin);
vbox = gtk_vbox_new(FALSE, 0);
/* combo */
profiles->pr_combo = gtk_combo_box_new_text();
for(i = 0; i < profiles->profiles_cnt; i++)
gtk_combo_box_append_text(GTK_COMBO_BOX(profiles->pr_combo),
profiles->profiles[i].name);
g_signal_connect_swapped(profiles->pr_combo, "changed", G_CALLBACK(
_on_settings_changed), plugin);
gtk_box_pack_start(GTK_BOX(vbox), profiles->pr_combo, FALSE, TRUE, 0);
/* frame */
frame = gtk_frame_new("Overview");
widget = gtk_vbox_new(FALSE, 4);
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);
bbox = gtk_hbox_new(FALSE, 4);
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();
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_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 */
bbox = gtk_hbutton_box_new();
gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 4);
widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK(
_on_settings_cancel), plugin);
gtk_container_add(GTK_CONTAINER(bbox), widget);
widget = gtk_button_new_from_stock(GTK_STOCK_OK);
g_signal_connect_swapped(G_OBJECT(widget), "clicked", G_CALLBACK(
_on_settings_ok), plugin);
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(plugin);
gtk_window_present(GTK_WINDOW(profiles->pr_window));
}
static gboolean _on_settings_closex(gpointer data)
{
PhonePlugin * plugin = data;
Profiles * profiles = plugin->priv;
_on_settings_cancel(profiles);
return TRUE;
}
static void _on_settings_cancel(gpointer data)
{
PhonePlugin * plugin = data;
Profiles * profiles = plugin->priv;
gtk_widget_hide(profiles->pr_window);
gtk_combo_box_set_active(GTK_COMBO_BOX(profiles->pr_combo),
profiles->profiles_cur);
}
static void _on_settings_changed(gpointer data)
{
PhonePlugin * plugin = data;
Profiles * profiles = plugin->priv;
int i;
char buf[16];
double fraction;
i = gtk_combo_box_get_active(GTK_COMBO_BOX(profiles->pr_combo));
if(i < 0)
return;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(profiles->pr_online),
_profiles_definitions[i].online);
fraction = _profiles_definitions[i].volume;
if(_profiles_definitions[i].volume > 0)
snprintf(buf, sizeof(buf), "%u %%",
_profiles_definitions[i].volume);
else if(_profiles_definitions[i].volume == 0)
snprintf(buf, sizeof(buf), "%s", "Silent");
else
{
snprintf(buf, sizeof(buf), "%s", "Ascending");
fraction = 0.0;
}
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_vibrator),
_profiles_definitions[i].vibrate);
}
static void _on_settings_ok(gpointer data)
{
PhonePlugin * plugin = data;
Profiles * profiles = plugin->priv;
ProfileType type;
gtk_widget_hide(profiles->pr_window);
type = gtk_combo_box_get_active(GTK_COMBO_BOX(profiles->pr_combo));
_profiles_switch(plugin, type);
}
/* profiles_apply */
static void _profiles_apply(PhonePlugin * plugin, ProfileType type)
{
PhonePluginHelper * helper = plugin->helper;
Profiles * profiles = plugin->priv;
if(type > profiles->profiles_cnt)
/* XXX report error */
return;
profiles->profiles_cur = type;
helper->config_set(helper->phone, "profiles", "default",
profiles->profiles[profiles->profiles_cur].name);
}
/* profiles_play */
static void _profiles_play(PhonePlugin * plugin, char const * sample,
int vibrator)
{
PhonePluginHelper * helper = plugin->helper;
Profiles * profiles = plugin->priv;
ProfileDefinition * definition = &profiles->profiles[
profiles->profiles_cur];
PhoneEvent event;
if(sample == NULL)
{
/* cancel the current sample */
if(profiles->pao != NULL)
pa_operation_cancel(profiles->pao);
profiles->pao = NULL;
}
else if(definition->volume != PROFILE_VOLUME_SILENT
&& profiles->pao == NULL)
/* FIXME apply the proper volume */
profiles->pao = pa_context_play_sample(profiles->pac, sample,
NULL, PA_VOLUME_NORM, NULL, NULL);
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(definition->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(500, _profiles_on_vibrate,
plugin);
}
}
/* profiles_switch */
static void _profiles_switch(PhonePlugin * plugin, ProfileType type)
{
PhonePluginHelper * helper = plugin->helper;
Profiles * profiles = plugin->priv;
ProfileType current = profiles->profiles_cur;
PhoneEvent pevent;
if(type == current)
return;
if(type > profiles->profiles_cnt)
/* XXX report error */
return;
_profiles_apply(plugin, type);
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)
{
PhonePlugin * plugin = data;
PhonePluginHelper * helper = plugin->helper;
Profiles * profiles = plugin->priv;
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;
}