aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorConor Curran <conor.curran@canonical.com>2011-02-18 17:53:29 +0000
committerConor Curran <conor.curran@canonical.com>2011-02-18 17:53:29 +0000
commitf82480c0cb0a801fa694a178a0021ed8370d07b7 (patch)
tree652e4449dc1654f37c60e6b796599a82d7187fb3
parent5d32cec559249cb0e3b0a85ecdc8e0a5dc1ab199 (diff)
parent223ff23be530c865da8a8b90d18ca77c36af6e35 (diff)
downloadayatana-indicator-sound-f82480c0cb0a801fa694a178a0021ed8370d07b7.tar.gz
ayatana-indicator-sound-f82480c0cb0a801fa694a178a0021ed8370d07b7.tar.bz2
ayatana-indicator-sound-f82480c0cb0a801fa694a178a0021ed8370d07b7.zip
voip feature landed
-rw-r--r--src/Makefile.am6
-rw-r--r--src/active-sink.c66
-rw-r--r--src/active-sink.h25
-rw-r--r--src/common-defs.h6
-rw-r--r--src/indicator-sound.c66
-rw-r--r--src/music-player-bridge.vala8
-rw-r--r--src/mute-menu-item.c1
-rw-r--r--src/player-item.vala3
-rw-r--r--src/pulseaudio-mgr.c209
-rw-r--r--src/pulseaudio-mgr.h2
-rw-r--r--src/settings-manager.vala2
-rw-r--r--src/sound-service-dbus.c8
-rw-r--r--src/sound-service-dbus.h3
-rw-r--r--src/sound-service.c2
-rw-r--r--src/voip-input-menu-item.c277
-rw-r--r--src/voip-input-menu-item.h70
-rw-r--r--src/voip-input-widget.c279
-rw-r--r--src/voip-input-widget.h55
18 files changed, 1044 insertions, 44 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 23bda3d..7525d71 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -20,6 +20,8 @@ libsoundmenu_la_SOURCES = \
title-widget.h \
volume-widget.c \
volume-widget.h \
+ voip-input-widget.c \
+ voip-input-widget.h \
gen-sound-service.xml.h \
gen-sound-service.xml.c \
dbus-shared-names.h
@@ -85,7 +87,7 @@ music_bridge_vala.stamp $(music_bridge_APIFILES): $(music_bridge_VALASOURCES)
# Sound Service C
###############################
indicator_sound_service_SOURCES = \
- common-defs.h \
+ common-defs.h \
sound-service.h \
sound-service.c \
pulseaudio-mgr.h \
@@ -96,6 +98,8 @@ indicator_sound_service_SOURCES = \
sound-service-dbus.c \
slider-menu-item.h \
slider-menu-item.c \
+ voip-input-menu-item.h \
+ voip-input-menu-item.c \
mute-menu-item.h \
mute-menu-item.c \
gen-sound-service.xml.h \
diff --git a/src/active-sink.c b/src/active-sink.c
index b7954be..a78d33e 100644
--- a/src/active-sink.c
+++ b/src/active-sink.c
@@ -21,7 +21,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "active-sink.h"
#include "slider-menu-item.h"
#include "mute-menu-item.h"
-
+#include "voip-input-menu-item.h"
#include "pulseaudio-mgr.h"
typedef struct _ActiveSinkPrivate ActiveSinkPrivate;
@@ -30,7 +30,8 @@ struct _ActiveSinkPrivate
{
SliderMenuItem* volume_slider_menuitem;
MuteMenuItem* mute_menuitem;
- SoundState current_sound_state;
+ VoipInputMenuItem* voip_input_menu_item;
+ SoundState current_sound_state;
SoundServiceDbus* service;
gint index;
gchar* name;
@@ -52,7 +53,6 @@ static pa_cvolume active_sink_construct_mono_volume (const pa_cvolume* vol);
static void active_sink_volume_update (ActiveSink* self, gdouble percent);
static void active_sink_mute_update (ActiveSink* self, gboolean muted);
-
G_DEFINE_TYPE (ActiveSink, active_sink, G_TYPE_OBJECT);
static void
@@ -72,6 +72,7 @@ active_sink_init (ActiveSink *self)
ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
priv->mute_menuitem = NULL;
priv->volume_slider_menuitem = NULL;
+ priv->voip_input_menu_item = NULL;
priv->current_sound_state = UNAVAILABLE;
priv->index = -1;
priv->name = NULL;
@@ -79,6 +80,7 @@ active_sink_init (ActiveSink *self)
// Init our menu items.
priv->mute_menuitem = g_object_new (MUTE_MENU_ITEM_TYPE, NULL);
+ priv->voip_input_menu_item = g_object_new (VOIP_INPUT_MENU_ITEM_TYPE, NULL);;
priv->volume_slider_menuitem = slider_menu_item_new (self);
mute_menu_item_enable (priv->mute_menuitem, FALSE);
slider_menu_item_enable (priv->volume_slider_menuitem, FALSE);
@@ -121,6 +123,32 @@ active_sink_populate (ActiveSink* sink,
}
void
+active_sink_activate_voip_item (ActiveSink* self, gint sink_input_index, gint client_index)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ if (voip_input_menu_item_is_interested (priv->voip_input_menu_item,
+ sink_input_index,
+ client_index)){
+ voip_input_menu_item_enable (priv->voip_input_menu_item, TRUE);
+ }
+}
+
+void
+active_sink_deactivate_voip_source (ActiveSink* self, gboolean visible)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ visible &= voip_input_menu_item_is_active (priv->voip_input_menu_item);
+ voip_input_menu_item_deactivate_source (priv->voip_input_menu_item, visible);
+}
+
+void
+active_sink_deactivate_voip_client (ActiveSink* self)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ voip_input_menu_item_deactivate_voip_client (priv->voip_input_menu_item);
+}
+
+void
active_sink_update (ActiveSink* sink,
const pa_sink_info* update)
{
@@ -168,6 +196,13 @@ active_sink_update_volume (ActiveSink* self, gdouble percent)
}
+gint
+active_sink_get_current_sink_input_index (ActiveSink* sink)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (sink);
+ return voip_input_menu_item_get_sink_input_index (priv->voip_input_menu_item);
+}
+
static void
active_sink_mute_update (ActiveSink* self, gboolean muted)
{
@@ -219,7 +254,7 @@ active_sink_get_state_from_volume (ActiveSink* self)
return state;
}
-static pa_cvolume
+pa_cvolume
active_sink_construct_mono_volume (const pa_cvolume* vol)
{
pa_cvolume new_volume;
@@ -279,6 +314,26 @@ active_sink_get_state (ActiveSink* self)
return priv->current_sound_state;
}
+void
+active_sink_update_voip_input_source (ActiveSink* self, const pa_source_info* update)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ voip_input_menu_item_update (priv->voip_input_menu_item, update);
+}
+
+gboolean
+active_sink_is_voip_source_populated (ActiveSink* self)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ return voip_input_menu_item_is_populated (priv->voip_input_menu_item);
+}
+
+gint active_sink_get_source_index (ActiveSink* self)
+{
+ ActiveSinkPrivate* priv = ACTIVE_SINK_GET_PRIVATE (self);
+ return voip_input_menu_item_get_index (priv->voip_input_menu_item);
+}
+
ActiveSink*
active_sink_new (SoundServiceDbus* service)
{
@@ -287,7 +342,8 @@ active_sink_new (SoundServiceDbus* service)
priv->service = service;
sound_service_dbus_build_sound_menu (service,
mute_menu_item_get_button (priv->mute_menuitem),
- DBUSMENU_MENUITEM (priv->volume_slider_menuitem));
+ DBUSMENU_MENUITEM (priv->volume_slider_menuitem),
+ DBUSMENU_MENUITEM (priv->voip_input_menu_item));
pm_establish_pulse_connection (sink);
return sink;
}
diff --git a/src/active-sink.h b/src/active-sink.h
index ab05ebc..57b3079 100644
--- a/src/active-sink.h
+++ b/src/active-sink.h
@@ -50,21 +50,34 @@ struct _ActiveSinkClass {
GType active_sink_get_type (void) G_GNUC_CONST;
+/**
+ * TODO
+ * Refactor this to become a device manager obj basically acting as wrapper for
+ * the communication between pulseaudio-mgr and the individual items.
+ * First steps collapse slider/volume related stuff into slider-menu-item.
+ */
+
+// Sink related
void active_sink_populate (ActiveSink* sink, const pa_sink_info* update);
void active_sink_update (ActiveSink* sink, const pa_sink_info* update);
-
gboolean active_sink_is_populated (ActiveSink* sink);
-void active_sink_determine_blocking_state (ActiveSink* self);
-
gint active_sink_get_index (ActiveSink* self);
-SoundState active_sink_get_state (ActiveSink* self);
-
void active_sink_deactivate (ActiveSink* self);
-
void active_sink_update_mute (ActiveSink* self, gboolean mute_update);
void active_sink_update_volume (ActiveSink* self, gdouble percent);
void active_sink_ensure_sink_is_unmuted (ActiveSink* self);
+// source and sinkinput/client related for VOIP functionality
+void active_sink_update_voip_input_source (ActiveSink* sink, const pa_source_info* update);
+void active_sink_activate_voip_item (ActiveSink* sink, gint sink_input_index, gint client_index);
+gint active_sink_get_current_sink_input_index (ActiveSink* sink);
+gboolean active_sink_is_voip_source_populated (ActiveSink* sink);
+gint active_sink_get_source_index (ActiveSink* self);
+void active_sink_determine_blocking_state (ActiveSink* self);
+void active_sink_deactivate_voip_source (ActiveSink* self, gboolean visible);
+void active_sink_deactivate_voip_client (ActiveSink* self);
+SoundState active_sink_get_state (ActiveSink* self);
+
ActiveSink* active_sink_new (SoundServiceDbus* service);
G_END_DECLS
diff --git a/src/common-defs.h b/src/common-defs.h
index 63d9d40..2184a48 100644
--- a/src/common-defs.h
+++ b/src/common-defs.h
@@ -31,13 +31,17 @@ typedef enum {
AVAILABLE
}SoundState;
-
+#define NOT_ACTIVE -1
#define DBUSMENU_PROPERTY_EMPTY -1
/* DBUS Custom Items */
#define DBUSMENU_VOLUME_MENUITEM_TYPE "x-canonical-ido-volume-type"
#define DBUSMENU_VOLUME_MENUITEM_LEVEL "x-canonical-ido-volume-level"
+#define DBUSMENU_VOIP_INPUT_MENUITEM_TYPE "x-canonical-ido-voip-input-type"
+#define DBUSMENU_VOIP_INPUT_MENUITEM_LEVEL "x-canonical-ido-voip-input-level"
+#define DBUSMENU_VOIP_INPUT_MENUITEM_MUTE "x-canonical-ido-voip-input-mute"
+
#define DBUSMENU_MUTE_MENUITEM_TYPE "x-canonical-sound-menu-mute-type"
#define DBUSMENU_MUTE_MENUITEM_VALUE "x-canonical-sound-menu-mute-value"
diff --git a/src/indicator-sound.c b/src/indicator-sound.c
index 96ab89b..2466550 100644
--- a/src/indicator-sound.c
+++ b/src/indicator-sound.c
@@ -32,12 +32,12 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
#include "metadata-widget.h"
#include "title-widget.h"
#include "volume-widget.h"
-
+#include "voip-input-widget.h"
#include "dbus-shared-names.h"
+#include "sound-state-manager.h"
#include "gen-sound-service.xml.h"
#include "common-defs.h"
-#include "sound-state-manager.h"
typedef struct _IndicatorSoundPrivate IndicatorSoundPrivate;
@@ -79,6 +79,10 @@ static gboolean new_volume_slider_widget (DbusmenuMenuitem * newitem,
DbusmenuMenuitem * parent,
DbusmenuClient * client,
gpointer user_data);
+static gboolean new_voip_slider_widget (DbusmenuMenuitem * newitem,
+ DbusmenuMenuitem * parent,
+ DbusmenuClient * client,
+ gpointer user_data);
static gboolean new_transport_widget (DbusmenuMenuitem * newitem,
DbusmenuMenuitem * parent,
DbusmenuClient * client,
@@ -191,6 +195,9 @@ get_menu (IndicatorObject * io)
DBUSMENU_VOLUME_MENUITEM_TYPE,
new_volume_slider_widget);
dbusmenu_client_add_type_handler (DBUSMENU_CLIENT(client),
+ DBUSMENU_VOIP_INPUT_MENUITEM_TYPE,
+ new_voip_slider_widget);
+ dbusmenu_client_add_type_handler (DBUSMENU_CLIENT(client),
DBUSMENU_TRANSPORT_MENUITEM_TYPE,
new_transport_widget);
dbusmenu_client_add_type_handler (DBUSMENU_CLIENT(client),
@@ -403,6 +410,53 @@ new_volume_slider_widget(DbusmenuMenuitem * newitem,
parent);
return TRUE;
}
+/**
+ * new_voip_slider_widget
+ * Create the voip menu item widget, must of the time this widget will be hidden.
+ * @param newitem
+ * @param parent
+ * @param client
+ * @param user_data
+ * @return
+ */
+static gboolean
+new_voip_slider_widget (DbusmenuMenuitem * newitem,
+ DbusmenuMenuitem * parent,
+ DbusmenuClient * client,
+ gpointer user_data)
+{
+ g_debug("indicator-sound: new_voip_slider_widget");
+ GtkWidget* voip_widget = NULL;
+ //IndicatorObject *io = NULL;
+
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE);
+ g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE);
+
+ voip_widget = voip_input_widget_new (newitem);
+/*
+ / io = g_object_get_data (G_OBJECT (client), "indicator");
+*/
+ //IndicatorSoundPrivate* priv = INDICATOR_SOUND_GET_PRIVATE(INDICATOR_SOUND (io));
+ //priv->volume_widget = volume_widget;
+
+ GtkWidget* ido_slider_widget = voip_input_widget_get_ido_slider(VOIP_INPUT_WIDGET(voip_widget));
+
+ gtk_widget_show_all(ido_slider_widget);
+ // register the style callback on this widget with state manager's style change
+ // handler (needs to remake the blocking animation for each style).
+/*
+ g_signal_connect (ido_slider_widget, "style-set",
+ G_CALLBACK(sound_state_manager_style_changed_cb),
+ priv->state_manager);
+*/
+
+ GtkMenuItem *menu_volume_item = GTK_MENU_ITEM(ido_slider_widget);
+ dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client),
+ newitem,
+ menu_volume_item,
+ parent);
+ return TRUE;
+}
/*******************************************************************/
//UI callbacks
@@ -439,19 +493,23 @@ key_press_cb(GtkWidget* widget, GdkEventKey* event, gpointer data)
switch (event->keyval) {
case GDK_Right:
digested = TRUE;
+/*
if (event->state & GDK_CONTROL_MASK) {
new_value = 100;
} else {
+*/
new_value = current_value + five_percent;
- }
+ //}
break;
case GDK_Left:
digested = TRUE;
+/*
if (event->state & GDK_CONTROL_MASK) {
new_value = 0;
} else {
+*/
new_value = current_value - five_percent;
- }
+ //}
break;
case GDK_plus:
digested = TRUE;
diff --git a/src/music-player-bridge.vala b/src/music-player-bridge.vala
index c6c9913..b5932fa 100644
--- a/src/music-player-bridge.vala
+++ b/src/music-player-bridge.vala
@@ -23,11 +23,13 @@ using GLib;
public class MusicPlayerBridge : GLib.Object
{
+ const int DEVICE_ITEMS_COUNT = 3;
+
private SettingsManager settings_manager;
private Dbusmenu.Menuitem root_menu;
private HashMap<string, PlayerController> registered_clients;
private Mpris2Watcher watcher;
-
+
public MusicPlayerBridge()
{
}
@@ -79,10 +81,10 @@ public class MusicPlayerBridge : GLib.Object
private int calculate_menu_position()
{
if(this.registered_clients.size == 0){
- return 2;
+ return DEVICE_ITEMS_COUNT;
}
else{
- return (2 + (this.registered_clients.size * PlayerController.WIDGET_QUANTITY));
+ return (DEVICE_ITEMS_COUNT + (this.registered_clients.size * PlayerController.WIDGET_QUANTITY));
}
}
diff --git a/src/mute-menu-item.c b/src/mute-menu-item.c
index 8409b9f..2876be3 100644
--- a/src/mute-menu-item.c
+++ b/src/mute-menu-item.c
@@ -61,6 +61,7 @@ mute_menu_item_init (MuteMenuItem *self)
{
g_debug("Building new Mute Menu Item");
MuteMenuItemPrivate* priv = MUTE_MENU_ITEM_GET_PRIVATE(self);
+ priv->button = NULL;
priv->button = dbusmenu_menuitem_new();
dbusmenu_menuitem_property_set_bool (priv->button,
DBUSMENU_MENUITEM_PROP_VISIBLE,
diff --git a/src/player-item.vala b/src/player-item.vala
index e146d4a..9d07bf7 100644
--- a/src/player-item.vala
+++ b/src/player-item.vala
@@ -94,9 +94,8 @@ public class PlayerItem : Dbusmenu.Menuitem
{
foreach(string prop in attrs){
//debug("populated ? - prop: %s", prop);
- int value_int = property_get_int(prop);
if(property_get_int(prop) != EMPTY){
- //debug("populated - prop %s and value %i", prop, value_int);
+ //debug("populated - prop %s and value %i", prop, property_get_int(prop));
return true;
}
}
diff --git a/src/pulseaudio-mgr.c b/src/pulseaudio-mgr.c
index 3aed1f1..76102e4 100644
--- a/src/pulseaudio-mgr.c
+++ b/src/pulseaudio-mgr.c
@@ -19,9 +19,9 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
/**Notes
*
- * Approach now is to set up the communication channels then query the server
- * fetch its default sink. If this fails then fetch the list of sinks and take
- * the first one which is not the auto-null sink.
+ * Approach now is to set up the communication channels, query the server
+ * fetch its default sink/source. If this fails then fetch the list of sinks/sources
+ * and take the first one which is not the auto-null sink.
* TODO: need to handle the situation where one chink in this linear chain breaks
* i.e. start off the process again and count the attempts (note different to
reconnect attempts)
@@ -47,10 +47,22 @@ static void pm_default_sink_info_callback (pa_context *c,
const pa_sink_info *info,
int eol,
void *userdata);
+static void pm_default_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata);
static void pm_sink_info_callback (pa_context *c,
const pa_sink_info *sink,
int eol,
void *userdata);
+static void pm_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata);
+static void pm_update_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata);
static void pm_sink_input_info_callback (pa_context *c,
const pa_sink_input_info *info,
int eol,
@@ -64,6 +76,7 @@ static void pm_toggle_mute_for_every_sink_callback (pa_context *c,
int eol,
void* userdata);
+
static gboolean reconnect_to_pulse (gpointer user_data);
static gint connection_attempts = 0;
@@ -152,6 +165,25 @@ pm_update_mute (gboolean update)
GINT_TO_POINTER (update)));
}
+void
+pm_update_mic_gain (gint source_index, pa_cvolume new_gain)
+{
+ pa_operation_unref (pa_context_set_source_volume_by_index (pulse_context,
+ source_index,
+ &new_gain,
+ NULL,
+ NULL) );
+}
+
+void
+pm_update_mic_mute (gint source_index, gint mute_update)
+{
+ pa_operation_unref (pa_context_set_source_mute_by_index (pulse_context,
+ source_index,
+ mute_update,
+ NULL,
+ NULL));
+}
/**********************************************************************************************************************/
// Pulse-Audio asychronous call-backs
/**********************************************************************************************************************/
@@ -163,19 +195,21 @@ pm_subscribed_events_callback (pa_context *c,
uint32_t index,
void* userdata)
{
+ if (IS_ACTIVE_SINK (userdata) == FALSE){
+ g_critical ("subscribed events callback - our userdata is not what we think it should be");
+ return;
+ }
+ ActiveSink* sink = ACTIVE_SINK (userdata);
+
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
case PA_SUBSCRIPTION_EVENT_SINK:
- if (IS_ACTIVE_SINK (userdata) == FALSE){
- g_warning ("subscribed events callback - our userdata is not what we think it should be");
- return;
- }
- ActiveSink* sink = ACTIVE_SINK (userdata);
+
// We don't care about any other sink other than the active one.
if (index != active_sink_get_index (sink))
- return;
+ return;
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
- active_sink_deactivate (ACTIVE_SINK (userdata));
+ active_sink_deactivate (sink);
}
else{
@@ -185,9 +219,36 @@ pm_subscribed_events_callback (pa_context *c,
userdata) );
}
break;
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ g_debug ("Looks like source event of some description");
+ // We don't care about any other sink other than the active one.
+ if (index != active_sink_get_source_index (sink))
+ return;
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ active_sink_deactivate_voip_source (sink, FALSE);
+ }
+ else{
+ pa_operation_unref (pa_context_get_source_info_by_index (c,
+ index,
+ pm_update_source_info_callback,
+ userdata) );
+ }
+ break;
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
// We don't care about sink input removals.
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_REMOVE) {
+ g_debug ("sink input event");
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ gint cached_index = active_sink_get_current_sink_input_index (sink);
+
+ g_debug ("Just saw a sink input removal event - index = %i and cached index = %i", index, cached_index);
+
+ if (index == cached_index){
+ active_sink_deactivate_voip_client (sink);
+ }
+ }
+ else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ g_debug ("some new sink input event ? - index = %i", index);
+ // Determine if its a VOIP app or a maybe blocking state.
pa_operation_unref (pa_context_get_sink_input_info (c,
index,
pm_sink_input_info_callback, userdata));
@@ -206,6 +267,7 @@ pm_subscribed_events_callback (pa_context *c,
}
+
static void
pm_context_state_callback (pa_context *c, void *userdata)
{
@@ -244,6 +306,7 @@ pm_context_state_callback (pa_context *c, void *userdata)
if (!(o = pa_context_subscribe (c, (pa_subscription_mask_t)
(PA_SUBSCRIPTION_MASK_SINK|
+ PA_SUBSCRIPTION_MASK_SOURCE|
PA_SUBSCRIPTION_MASK_SINK_INPUT|
PA_SUBSCRIPTION_MASK_SERVER), NULL, NULL))) {
g_warning("pa_context_subscribe() failed");
@@ -261,7 +324,8 @@ pm_context_state_callback (pa_context *c, void *userdata)
/**
After startup we go straight for the server info to see if it has details of
- the default sink. If so it makes things much easier.
+ the default sink and source. Normally these are valid, if there is none set
+ fetch the list of each and try to determine the sink.
**/
static void
pm_server_info_callback (pa_context *c,
@@ -276,24 +340,46 @@ pm_server_info_callback (pa_context *c,
active_sink_deactivate (ACTIVE_SINK (userdata));
return;
}
+ // Go for the default sink
if (info->default_sink_name != NULL) {
g_debug ("default sink name from the server ain't null'");
if (!(operation = pa_context_get_sink_info_by_name (c,
info->default_sink_name,
pm_default_sink_info_callback,
userdata) )) {
- }
- else{
+ g_warning("pa_context_get_sink_info_by_namet() failed");
+ active_sink_deactivate (ACTIVE_SINK (userdata));
pa_operation_unref(operation);
return;
}
- }
+ } // If there is no default sink, try to determine a sink from the list of sinks
else if (!(operation = pa_context_get_sink_info_list(c,
pm_sink_info_callback,
- NULL))) {
+ userdata))) {
g_warning("pa_context_get_sink_info_list() failed");
+ active_sink_deactivate (ACTIVE_SINK (userdata));
+ pa_operation_unref(operation);
return;
}
+ // And the source
+ if (info->default_source_name != NULL) {
+ g_debug ("default source name from the server is not null'");
+ if (!(operation = pa_context_get_source_info_by_name (c,
+ info->default_source_name,
+ pm_default_source_info_callback,
+ userdata) )) {
+ g_warning("pa_context_get_default_source_info() failed");
+ // TODO: call some input deactivate method on active sink
+ pa_operation_unref(operation);
+ return;
+ }
+ }
+ else if (!(operation = pa_context_get_source_info_list(c,
+ pm_source_info_callback,
+ userdata))) {
+ g_warning("pa_context_get_sink_info_list() failed");
+ // TODO: call some input deactivate method for the source
+ }
pa_operation_unref(operation);
}
@@ -335,8 +421,12 @@ pm_default_sink_info_callback (pa_context *c,
if (IS_ACTIVE_SINK (userdata) == FALSE){
g_warning ("Default sink info callback - our user data is not what we think it should be");
return;
- }
- g_debug ("server has handed us a default sink");
+ }
+ // Only repopulate if there is a change with regards the index
+ if (active_sink_get_index (ACTIVE_SINK (userdata)) == info->index)
+ return;
+
+ g_debug ("Pulse Server has handed us a new default sink");
active_sink_populate (ACTIVE_SINK (userdata), info);
}
}
@@ -356,12 +446,30 @@ pm_sink_input_info_callback (pa_context *c,
g_warning("\n Sink input info callback : SINK INPUT INFO IS NULL BUT EOL was not POSITIVE!!!");
return;
}
+
if (IS_ACTIVE_SINK (userdata) == FALSE){
g_warning ("sink input info callback - our user data is not what we think it should be");
return;
}
-
+ // Check if this is Voip sink input
+ gint result = pa_proplist_contains (info->proplist, PA_PROP_MEDIA_ROLE);
ActiveSink* a_sink = ACTIVE_SINK (userdata);
+
+ if (result == 1){
+ g_debug ("Sink input info has media role property");
+ const char* value = pa_proplist_gets (info->proplist, PA_PROP_MEDIA_ROLE);
+ g_debug ("prop role = %s", value);
+ if (g_strcmp0 (value, "phone") == 0) {
+ g_debug ("And yes its a VOIP app ... sink input index = %i", info->index);
+ active_sink_activate_voip_item (a_sink, (gint)info->index, (gint)info->client);
+ // TODO to start with we will assume our source is the same as what this 'client'
+ // is pointing at. This should probably be more intelligent :
+ // query for the list of source output info's and going on the name of the client
+ // from the sink input ensure our voip item is using the right source.
+ }
+ }
+
+ // And finally check for the mute blocking state
if (active_sink_get_index (a_sink) == info->sink){
active_sink_determine_blocking_state (a_sink);
}
@@ -404,3 +512,66 @@ pm_toggle_mute_for_every_sink_callback (pa_context *c,
}
}
+// Source info related callbacks
+static void
+pm_default_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata)
+{
+ if (eol > 0) {
+ return;
+ }
+ else {
+ if (IS_ACTIVE_SINK (userdata) == FALSE){
+ g_warning ("Default sink info callback - our user data is not what we think it should be");
+ return;
+ }
+ // If there is an index change we need to change our cached source
+ if (active_sink_get_source_index (ACTIVE_SINK (userdata)) == info->index)
+ return;
+ g_debug ("Pulse Server has handed us a new default source");
+ active_sink_deactivate_voip_source (ACTIVE_SINK (userdata), TRUE);
+ active_sink_update_voip_input_source (ACTIVE_SINK (userdata), info);
+ }
+}
+
+static void
+pm_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata)
+{
+ if (eol > 0) {
+ return;
+ }
+ else {
+ if (IS_ACTIVE_SINK (userdata) == FALSE){
+ g_warning ("Default sink info callback - our user data is not what we think it should be");
+ return;
+ }
+ // For now we will take the first available
+ if (active_sink_is_voip_source_populated (ACTIVE_SINK (userdata)) == FALSE){
+ active_sink_update_voip_input_source (ACTIVE_SINK (userdata), info);
+ }
+ }
+}
+
+static void
+pm_update_source_info_callback (pa_context *c,
+ const pa_source_info *info,
+ int eol,
+ void *userdata)
+{
+ if (eol > 0) {
+ return;
+ }
+ else {
+ if (IS_ACTIVE_SINK (userdata) == FALSE){
+ g_warning ("Default sink info callback - our user data is not what we think it should be");
+ return;
+ }
+ g_debug ("Got a source update for %s , index %i", info->name, info->index);
+ active_sink_update_voip_input_source (ACTIVE_SINK (userdata), info);
+ }
+} \ No newline at end of file
diff --git a/src/pulseaudio-mgr.h b/src/pulseaudio-mgr.h
index c0ab9c0..d61117d 100644
--- a/src/pulseaudio-mgr.h
+++ b/src/pulseaudio-mgr.h
@@ -22,6 +22,8 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
void pm_establish_pulse_connection (ActiveSink* active_sink);
void close_pulse_activites();
void pm_update_volume (gint sink_index, pa_cvolume new_volume);
+void pm_update_mic_gain (gint source_index, pa_cvolume new_gain);
+void pm_update_mic_mute (gint source_index, int mute_update);
void pm_update_mute (gboolean update);
diff --git a/src/settings-manager.vala b/src/settings-manager.vala
index 057a47b..6800423 100644
--- a/src/settings-manager.vala
+++ b/src/settings-manager.vala
@@ -71,6 +71,7 @@ public class SettingsManager : GLib.Object
// Convenient debug method inorder to provide visability over
// the contents of both interested and blacklisted containers in its gsettings
+/**
private void reveal_contents()
{
var already_interested = this.settings.get_strv ("interested-media-players");
@@ -87,4 +88,5 @@ public class SettingsManager : GLib.Object
debug ("interested array size = %i", already_interested.length);
debug ("blacklisted array size = %i", blacklisted.length);
}
+**/
}
diff --git a/src/sound-service-dbus.c b/src/sound-service-dbus.c
index 58367f4..9be39c7 100644
--- a/src/sound-service-dbus.c
+++ b/src/sound-service-dbus.c
@@ -157,14 +157,18 @@ sound_service_dbus_create_root_item (SoundServiceDbus* self)
void
sound_service_dbus_build_sound_menu ( SoundServiceDbus* self,
DbusmenuMenuitem* mute_item,
- DbusmenuMenuitem* slider_item)
+ DbusmenuMenuitem* slider_item,
+ DbusmenuMenuitem* voip_input_menu_item)
{
SoundServiceDbusPrivate * priv = SOUND_SERVICE_DBUS_GET_PRIVATE(self);
// Mute button
+ // TODO this additions should be fixed position, i.e. add via position and not just append
+ // be explicit as it is fixed.
dbusmenu_menuitem_child_append (priv->root_menuitem, mute_item);
- g_debug ("just about to add the slider %i", DBUSMENU_IS_MENUITEM(slider_item));
dbusmenu_menuitem_child_append (priv->root_menuitem, slider_item);
+ g_debug ("just about to add the slider %i", DBUSMENU_IS_MENUITEM(slider_item));
+ dbusmenu_menuitem_child_append (priv->root_menuitem, voip_input_menu_item);
// Separator
DbusmenuMenuitem* separator = dbusmenu_menuitem_new();
diff --git a/src/sound-service-dbus.h b/src/sound-service-dbus.h
index cdc4608..9b19a5e 100644
--- a/src/sound-service-dbus.h
+++ b/src/sound-service-dbus.h
@@ -57,7 +57,8 @@ DbusmenuMenuitem* sound_service_dbus_create_root_item (SoundServiceDbus* self);
void sound_service_dbus_update_sound_state (SoundServiceDbus* self, SoundState new_state);
void sound_service_dbus_build_sound_menu ( SoundServiceDbus* self,
DbusmenuMenuitem* mute_item,
- DbusmenuMenuitem* slider_item);
+ DbusmenuMenuitem* slider_item,
+ DbusmenuMenuitem* voip_input_menu_item);
G_END_DECLS
diff --git a/src/sound-service.c b/src/sound-service.c
index cfc0b7e..c79b9f6 100644
--- a/src/sound-service.c
+++ b/src/sound-service.c
@@ -39,8 +39,10 @@ service_shutdown (IndicatorService *service, gpointer user_data)
{
if (mainloop != NULL) {
g_debug("Service shutdown !");
+
close_pulse_activites();
g_main_loop_quit(mainloop);
+
}
return;
}
diff --git a/src/voip-input-menu-item.c b/src/voip-input-menu-item.c
new file mode 100644
index 0000000..a742654
--- /dev/null
+++ b/src/voip-input-menu-item.c
@@ -0,0 +1,277 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+*/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include "voip-input-menu-item.h"
+#include "common-defs.h"
+#include "pulseaudio-mgr.h"
+
+typedef struct _VoipInputMenuItemPrivate VoipInputMenuItemPrivate;
+
+struct _VoipInputMenuItemPrivate {
+ ActiveSink* a_sink;
+ pa_cvolume volume;
+ gint mute;
+ guint32 volume_steps;
+ pa_channel_map channel_map;
+ pa_volume_t base_volume;
+ gint source_index;
+ gint sink_input_index;
+ gint client_index;
+};
+
+#define VOIP_INPUT_MENU_ITEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), VOIP_INPUT_MENU_ITEM_TYPE, VoipInputMenuItemPrivate))
+
+/* Prototypes */
+static void voip_input_menu_item_class_init (VoipInputMenuItemClass *klass);
+static void voip_input_menu_item_init (VoipInputMenuItem *self);
+static void voip_input_menu_item_dispose (GObject *object);
+static void voip_input_menu_item_finalize (GObject *object);
+static void handle_event (DbusmenuMenuitem * mi, const gchar * name,
+ GVariant * value, guint timestamp);
+// TODO:
+// This method should really be shared between this and the volume slider obj
+// perfectly static - wait until the device mgr wrapper is properly sorted and
+// then consolidate
+static pa_cvolume voip_input_menu_item_construct_mono_volume (const pa_cvolume* vol);
+
+G_DEFINE_TYPE (VoipInputMenuItem, voip_input_menu_item, DBUSMENU_TYPE_MENUITEM);
+
+static void
+voip_input_menu_item_class_init (VoipInputMenuItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (VoipInputMenuItemPrivate));
+
+ object_class->dispose = voip_input_menu_item_dispose;
+ object_class->finalize = voip_input_menu_item_finalize;
+
+ DbusmenuMenuitemClass * mclass = DBUSMENU_MENUITEM_CLASS(klass);
+ mclass->handle_event = handle_event;
+}
+
+static void
+voip_input_menu_item_init (VoipInputMenuItem *self)
+{
+ g_debug("Building new Slider Menu Item");
+ dbusmenu_menuitem_property_set( DBUSMENU_MENUITEM(self),
+ DBUSMENU_MENUITEM_PROP_TYPE,
+ DBUSMENU_VOIP_INPUT_MENUITEM_TYPE );
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (self);
+ dbusmenu_menuitem_property_set_bool( DBUSMENU_MENUITEM(self),
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ FALSE );
+
+ priv->source_index = NOT_ACTIVE;
+ priv->sink_input_index = NOT_ACTIVE;
+ priv->client_index = NOT_ACTIVE;
+ priv->mute = NOT_ACTIVE;
+}
+
+static void
+voip_input_menu_item_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (voip_input_menu_item_parent_class)->dispose (object);
+ return;
+}
+
+static void
+voip_input_menu_item_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (voip_input_menu_item_parent_class)->finalize (object);
+}
+
+static void
+handle_event (DbusmenuMenuitem * mi,
+ const gchar * name,
+ GVariant * value,
+ guint timestamp)
+{
+ GVariant* input = NULL;
+ input = value;
+ if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT) == TRUE) {
+ input = g_variant_get_variant(value);
+ }
+
+ gdouble percent = g_variant_get_double(input);
+ if (value != NULL){
+ if (IS_VOIP_INPUT_MENU_ITEM (mi)) {
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (VOIP_INPUT_MENU_ITEM (mi));
+ g_debug ("Handle event in the voip input level backend instance - %f", percent);
+ pa_cvolume new_volume;
+ pa_cvolume_init(&new_volume);
+ new_volume.channels = 1;
+ pa_volume_t new_volume_value = (pa_volume_t) ((percent * PA_VOLUME_NORM) / 100);
+ pa_cvolume_set(&new_volume, 1, new_volume_value);
+
+ pm_update_mic_gain (priv->source_index, new_volume);
+ // finally unmute if needed
+ if (priv->mute == 1) {
+ pm_update_mic_mute (priv->source_index, 0);
+ }
+ //active_sink_update_volume (priv->a_sink, volume_input);
+ //active_sink_ensure_sink_is_unmuted (priv->a_sink);
+ }
+ }
+}
+
+static pa_cvolume
+voip_input_menu_item_construct_mono_volume (const pa_cvolume* vol)
+{
+ pa_cvolume new_volume;
+ pa_cvolume_init(&new_volume);
+ new_volume.channels = 1;
+ pa_volume_t max_vol = pa_cvolume_max(vol);
+ pa_cvolume_set(&new_volume, 1, max_vol);
+ return new_volume;
+}
+
+void
+voip_input_menu_item_update (VoipInputMenuItem* item,
+ const pa_source_info* source)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ // only overwrite the constants of each source if the device has changed
+ if (priv->source_index == NOT_ACTIVE){
+ priv->base_volume = source->base_volume;
+ priv->volume_steps = source->n_volume_steps;
+ priv->channel_map = source->channel_map;
+ priv->source_index = source->index;
+ }
+ priv->volume = voip_input_menu_item_construct_mono_volume (&source->volume);
+ pa_volume_t vol = pa_cvolume_max (&source->volume);
+ gdouble update = ((gdouble) vol * 100) / PA_VOLUME_NORM;
+
+ GVariant* new_volume = g_variant_new_double(update);
+ dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(item),
+ DBUSMENU_VOIP_INPUT_MENUITEM_LEVEL,
+ new_volume);
+ // Only send over the mute updates if the state has changed.
+ // in this order - volume first mute last!!
+ if (priv->mute != source->mute){
+ g_debug ("voip menu item - update - mute = %i", priv->mute);
+ GVariant* new_mute_update = g_variant_new_int32 (source->mute);
+ dbusmenu_menuitem_property_set_variant (DBUSMENU_MENUITEM(item),
+ DBUSMENU_VOIP_INPUT_MENUITEM_MUTE,
+ new_mute_update);
+ }
+
+ priv->mute = source->mute;
+
+}
+
+gboolean
+voip_input_menu_item_is_interested (VoipInputMenuItem* item,
+ gint sink_input_index,
+ gint client_index)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ // Check to make sure we are not handling another voip beforehand and that we
+ // have an active sink (might need to match up at start up)
+ if (priv->sink_input_index != NOT_ACTIVE &&
+ priv->source_index != NOT_ACTIVE){
+ return FALSE;
+ }
+
+ priv->sink_input_index = sink_input_index;
+ priv->client_index = client_index;
+
+ return TRUE;
+}
+
+gboolean
+voip_input_menu_item_is_active (VoipInputMenuItem* item)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ return (priv->sink_input_index != NOT_ACTIVE && priv->client_index != NOT_ACTIVE);
+}
+
+
+gboolean
+voip_input_menu_item_is_populated (VoipInputMenuItem* item)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ return priv->source_index != NOT_ACTIVE;
+}
+
+gint
+voip_input_menu_item_get_index (VoipInputMenuItem* item)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ return priv->source_index;
+}
+
+gint
+voip_input_menu_item_get_sink_input_index (VoipInputMenuItem* item)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+
+ return priv->sink_input_index;
+}
+
+/**
+ * If the pulse server informs of a default source change
+ * or the source in question is removed.
+ * @param item
+ */
+void
+voip_input_menu_item_deactivate_source (VoipInputMenuItem* item, gboolean visible)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ priv->source_index = NOT_ACTIVE;
+ dbusmenu_menuitem_property_set_bool( DBUSMENU_MENUITEM(item),
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ visible );
+}
+
+void
+voip_input_menu_item_deactivate_voip_client (VoipInputMenuItem* item)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ priv->client_index = NOT_ACTIVE;
+ priv->sink_input_index = NOT_ACTIVE;
+ voip_input_menu_item_enable (item, FALSE);
+}
+
+void
+voip_input_menu_item_enable (VoipInputMenuItem* item,
+ gboolean active)
+{
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (item);
+ if (priv->source_index == NOT_ACTIVE && active == TRUE) {
+ g_warning ("Tried to enable the VOIP menuitem but we don't have an active source ??");
+ active = FALSE;
+ }
+ dbusmenu_menuitem_property_set_bool( DBUSMENU_MENUITEM(item),
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ active );
+}
+
+VoipInputMenuItem*
+voip_input_menu_item_new (ActiveSink* sink)
+{
+ VoipInputMenuItem *self = g_object_new(VOIP_INPUT_MENU_ITEM_TYPE, NULL);
+ VoipInputMenuItemPrivate* priv = VOIP_INPUT_MENU_ITEM_GET_PRIVATE (self);
+ priv->a_sink = sink;
+ return self;
+} \ No newline at end of file
diff --git a/src/voip-input-menu-item.h b/src/voip-input-menu-item.h
new file mode 100644
index 0000000..6f4ed85
--- /dev/null
+++ b/src/voip-input-menu-item.h
@@ -0,0 +1,70 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+*/
+#ifndef __VOIP_INPUT_MENU_ITEM_H__
+#define __VOIP_INPUT_MENU_ITEM_H__
+
+#include <glib.h>
+#include <pulse/pulseaudio.h>
+#include <libdbusmenu-glib/menuitem.h>
+#include "active-sink.h"
+
+G_BEGIN_DECLS
+
+#define VOIP_INPUT_MENU_ITEM_TYPE (voip_input_menu_item_get_type ())
+#define VOIP_INPUT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), VOIP_INPUT_MENU_ITEM_TYPE, VoipInputMenuItem))
+#define VOIP_INPUT_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), VOIP_INPUT_MENU_ITEM_TYPE, VoipInputMenuItemClass))
+#define IS_VOIP_INPUT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VOIP_INPUT_MENU_ITEM_TYPE))
+#define IS_VOIP_INPUT_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), VOIP_INPUT_MENU_ITEM_TYPE))
+#define VOIP_INPUT_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), VOIP_INPUT_MENU_ITEM_TYPE, VoipInputMenuItemClass))
+
+typedef struct _VoipInputMenuItem VoipInputMenuItem;
+typedef struct _VoipInputMenuItemClass VoipInputMenuItemClass;
+
+struct _VoipInputMenuItemClass {
+ DbusmenuMenuitemClass parent_class;
+};
+
+struct _VoipInputMenuItem {
+ DbusmenuMenuitem parent;
+};
+
+GType voip_input_menu_item_get_type (void);
+
+void voip_input_menu_item_update (VoipInputMenuItem* item,
+ const pa_source_info* source);
+void voip_input_menu_item_enable (VoipInputMenuItem* item, gboolean active);
+gboolean voip_input_menu_item_is_interested (VoipInputMenuItem* item,
+ gint sink_input_index,
+ gint client_index);
+gboolean voip_input_menu_item_is_active (VoipInputMenuItem* item);
+gboolean voip_input_menu_item_is_populated (VoipInputMenuItem* item);
+// TODO rename get source index
+gint voip_input_menu_item_get_index (VoipInputMenuItem* item);
+
+gint voip_input_menu_item_get_sink_input_index (VoipInputMenuItem* item);
+
+void voip_input_menu_item_deactivate_source (VoipInputMenuItem* item, gboolean visible);
+void voip_input_menu_item_deactivate_voip_client (VoipInputMenuItem* item);
+
+VoipInputMenuItem* voip_input_menu_item_new (ActiveSink* sink);
+
+G_END_DECLS
+
+#endif
+
diff --git a/src/voip-input-widget.c b/src/voip-input-widget.c
new file mode 100644
index 0000000..9b29feb
--- /dev/null
+++ b/src/voip-input-widget.c
@@ -0,0 +1,279 @@
+
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <math.h>
+#include <glib.h>
+#include "voip-input-widget.h"
+#include "common-defs.h"
+#include <libido/idoscalemenuitem.h>
+
+typedef struct _VoipInputWidgetPrivate VoipInputWidgetPrivate;
+
+struct _VoipInputWidgetPrivate
+{
+ DbusmenuMenuitem* twin_item;
+ GtkWidget* ido_voip_input_slider;
+ gboolean grabbed;
+};
+
+#define VOIP_INPUT_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), VOIP_INPUT_WIDGET_TYPE, VoipInputWidgetPrivate))
+
+/* Prototypes */
+static void voip_input_widget_class_init (VoipInputWidgetClass *klass);
+static void voip_input_widget_init (VoipInputWidget *self);
+static void voip_input_widget_dispose (GObject *object);
+static void voip_input_widget_finalize (GObject *object);
+static void voip_input_widget_set_twin_item( VoipInputWidget* self,
+ DbusmenuMenuitem* twin_item);
+static void voip_input_widget_property_update( DbusmenuMenuitem* item, gchar* property,
+ GVariant* value, gpointer userdata );
+
+static gboolean voip_input_widget_change_value_cb (GtkRange *range,
+ GtkScrollType scroll,
+ gdouble value,
+ gpointer user_data);
+static gboolean voip_input_widget_value_changed_cb(GtkRange *range, gpointer user_data);
+static void voip_input_widget_slider_grabbed(GtkWidget *widget, gpointer user_data);
+static void voip_input_widget_slider_released(GtkWidget *widget, gpointer user_data);
+static void voip_input_widget_parent_changed (GtkWidget *widget, gpointer user_data);
+
+G_DEFINE_TYPE (VoipInputWidget, voip_input_widget, G_TYPE_OBJECT);
+
+
+static void
+voip_input_widget_class_init (VoipInputWidgetClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (VoipInputWidgetPrivate));
+
+ gobject_class->dispose = voip_input_widget_dispose;
+ gobject_class->finalize = voip_input_widget_finalize;
+}
+
+static void
+voip_input_widget_init (VoipInputWidget *self)
+{
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(self);
+
+ priv->ido_voip_input_slider = ido_scale_menu_item_new_with_range ("VOLUME", IDO_RANGE_STYLE_DEFAULT, 0, 0, 100, 1);
+ g_object_ref (priv->ido_voip_input_slider);
+ ido_scale_menu_item_set_style (IDO_SCALE_MENU_ITEM (priv->ido_voip_input_slider), IDO_SCALE_MENU_ITEM_STYLE_IMAGE);
+ g_object_set(priv->ido_voip_input_slider, "reverse-scroll-events", TRUE, NULL);
+
+ g_signal_connect (priv->ido_voip_input_slider,
+ "notify::parent", G_CALLBACK (voip_input_widget_parent_changed),
+ NULL);
+
+ GtkWidget* voip_input_widget = ido_scale_menu_item_get_scale((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+
+ g_signal_connect(voip_input_widget, "change-value", G_CALLBACK(voip_input_widget_change_value_cb), self);
+ g_signal_connect(voip_input_widget, "value-changed", G_CALLBACK(voip_input_widget_value_changed_cb), self);
+ g_signal_connect(priv->ido_voip_input_slider, "slider-grabbed", G_CALLBACK(voip_input_widget_slider_grabbed), self);
+ g_signal_connect(priv->ido_voip_input_slider, "slider-released", G_CALLBACK(voip_input_widget_slider_released), self);
+
+ GtkWidget* primary_image = ido_scale_menu_item_get_primary_image((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ GIcon * primary_gicon = g_themed_icon_new_with_default_fallbacks("audio-input-microphone");
+ gtk_image_set_from_gicon(GTK_IMAGE(primary_image), primary_gicon, GTK_ICON_SIZE_MENU);
+ g_object_unref(primary_gicon);
+
+ GtkWidget* secondary_image = ido_scale_menu_item_get_secondary_image((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ GIcon * secondary_gicon = g_themed_icon_new_with_default_fallbacks("audio-input-microphone-high");
+ gtk_image_set_from_gicon(GTK_IMAGE(secondary_image), secondary_gicon, GTK_ICON_SIZE_MENU);
+ g_object_unref(secondary_gicon);
+
+ GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (voip_input_widget));
+ gtk_adjustment_set_step_increment(adj, 4);
+}
+
+static void
+voip_input_widget_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (voip_input_widget_parent_class)->dispose (object);
+}
+
+static void
+voip_input_widget_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (voip_input_widget_parent_class)->finalize (object);
+}
+
+static void
+voip_input_widget_property_update (DbusmenuMenuitem* item, gchar* property,
+ GVariant* value, gpointer userdata)
+{
+ g_return_if_fail (IS_VOIP_INPUT_WIDGET (userdata));
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(userdata);
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(mitem);
+ //g_debug("scrub-widget::property_update for prop %s", property);
+ if(g_ascii_strcasecmp(DBUSMENU_VOIP_INPUT_MENUITEM_LEVEL, property) == 0){
+ if(priv->grabbed == FALSE){
+ GtkWidget *slider = ido_scale_menu_item_get_scale((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ GtkRange *range = (GtkRange*)slider;
+ gdouble update = g_variant_get_double (value);
+ //g_debug("volume-widget - update level with value %f", update);
+ gtk_range_set_value(range, update);
+ }
+ }
+ if(g_ascii_strcasecmp(DBUSMENU_VOIP_INPUT_MENUITEM_MUTE, property) == 0){
+ if(priv->grabbed == FALSE){
+ GtkWidget *slider = ido_scale_menu_item_get_scale((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ GtkRange *range = (GtkRange*)slider;
+ gint update = g_variant_get_int32 (value);
+ gdouble level;
+ if (update == 1){
+ level = 0;
+ }
+ else{
+ level = g_variant_get_double (dbusmenu_menuitem_property_get_variant (priv->twin_item,
+ DBUSMENU_VOIP_INPUT_MENUITEM_LEVEL));
+ }
+ gtk_range_set_value(range, level);
+
+ g_debug ("voip-item-widget - update mute with value %i", update);
+ }
+ }
+}
+
+static void
+voip_input_widget_set_twin_item (VoipInputWidget* self,
+ DbusmenuMenuitem* twin_item)
+{
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(self);
+ priv->twin_item = twin_item;
+ g_object_ref(priv->twin_item);
+ g_signal_connect(G_OBJECT(twin_item), "property-changed",
+ G_CALLBACK(voip_input_widget_property_update), self);
+ gdouble initial_level = g_variant_get_double (dbusmenu_menuitem_property_get_variant(twin_item,
+ DBUSMENU_VOIP_INPUT_MENUITEM_LEVEL));
+ //g_debug("voip_input_widget_set_twin_item initial level = %f", initial_level);
+ GtkWidget *slider = ido_scale_menu_item_get_scale((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ GtkRange *range = (GtkRange*)slider;
+ gtk_range_set_value(range, initial_level);
+
+ gint mute = g_variant_get_int32 (dbusmenu_menuitem_property_get_variant (priv->twin_item,
+ DBUSMENU_VOIP_INPUT_MENUITEM_MUTE));
+ if (mute == 1){
+ gtk_range_set_value (range, 0.0);
+ }
+}
+
+static gboolean
+voip_input_widget_change_value_cb (GtkRange *range,
+ GtkScrollType scroll,
+ gdouble new_value,
+ gpointer user_data)
+{
+ g_return_val_if_fail (IS_VOIP_INPUT_WIDGET (user_data), FALSE);
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(user_data);
+ voip_input_widget_update(mitem, new_value);
+ return FALSE;
+}
+
+
+/**
+ * We only want this callback to catch mouse icon press events which set the
+ * slider to 0 or 100. Ignore all other events including the new Mute behaviour
+ * (slider to go to 0 on mute without setting the level to 0 and return to
+ * previous level on unmute)
+ **/
+static gboolean
+voip_input_widget_value_changed_cb(GtkRange *range, gpointer user_data)
+{
+ g_return_val_if_fail (IS_VOIP_INPUT_WIDGET (user_data), FALSE);
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(user_data);
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(mitem);
+ GtkWidget *slider = ido_scale_menu_item_get_scale((IdoScaleMenuItem*)priv->ido_voip_input_slider);
+ gdouble current_value = CLAMP(gtk_range_get_value(GTK_RANGE(slider)), 0, 100);
+
+ gint mute = g_variant_get_int32 (dbusmenu_menuitem_property_get_variant (priv->twin_item,
+ DBUSMENU_VOIP_INPUT_MENUITEM_MUTE));
+ if ((current_value == 0 && mute != 1) || current_value == 100 ){
+ voip_input_widget_update(mitem, current_value);
+ }
+ return FALSE;
+}
+
+void
+voip_input_widget_update(VoipInputWidget* self, gdouble update)
+{
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(self);
+ gdouble clamped = CLAMP(update, 0, 100);
+ GVariant* new_volume = g_variant_new_double(clamped);
+ dbusmenu_menuitem_handle_event (priv->twin_item, "update", new_volume, 0);
+}
+
+GtkWidget*
+voip_input_widget_get_ido_slider(VoipInputWidget* self)
+{
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(self);
+ return priv->ido_voip_input_slider;
+}
+
+static void
+voip_input_widget_parent_changed (GtkWidget *widget,
+ gpointer user_data)
+{
+ gtk_widget_set_size_request (widget, 200, -1);
+ //g_debug("voip_input_widget_parent_changed");
+}
+
+static void
+voip_input_widget_slider_grabbed(GtkWidget *widget, gpointer user_data)
+{
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(user_data);
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(mitem);
+ priv->grabbed = TRUE;
+}
+
+static void
+voip_input_widget_slider_released(GtkWidget *widget, gpointer user_data)
+{
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(user_data);
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(mitem);
+ priv->grabbed = FALSE;
+}
+
+void
+voip_input_widget_tidy_up (GtkWidget *widget)
+{
+ VoipInputWidget* mitem = VOIP_INPUT_WIDGET(widget);
+ VoipInputWidgetPrivate * priv = VOIP_INPUT_WIDGET_GET_PRIVATE(mitem);
+ gtk_widget_destroy (priv->ido_voip_input_slider);
+}
+
+/**
+ * voip_input_widget_new:
+ * @returns: a new #VoipInputWidget.
+ **/
+GtkWidget*
+voip_input_widget_new(DbusmenuMenuitem *item)
+{
+ GtkWidget* widget = g_object_new(VOIP_INPUT_WIDGET_TYPE, NULL);
+ voip_input_widget_set_twin_item((VoipInputWidget*)widget, item);
+ return widget;
+}
+
+
diff --git a/src/voip-input-widget.h b/src/voip-input-widget.h
new file mode 100644
index 0000000..29912f0
--- /dev/null
+++ b/src/voip-input-widget.h
@@ -0,0 +1,55 @@
+/*
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Conor Curran <conor.curran@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
+*/
+#ifndef __VOIP_INPUT_WIDGET_H__
+#define __VOIP_INPUT_WIDGET_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libdbusmenu-gtk/menuitem.h>
+
+G_BEGIN_DECLS
+
+#define VOIP_INPUT_WIDGET_TYPE (voip_input_widget_get_type ())
+#define VOIP_INPUT_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), VOIP_INPUT_WIDGET_TYPE, VoipInputWidget))
+#define VOIP_INPUT_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), VOIP_INPUT_WIDGET_TYPE, VoipInputWidgetClass))
+#define IS_VOIP_INPUT_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VOIP_INPUT_WIDGET_TYPE))
+#define IS_VOIP_INPUT_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), VOIP_INPUT_WIDGET_TYPE))
+#define VOIP_INPUT_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), VOIP_INPUT_WIDGET_TYPE, VoipInputWidgetClass))
+
+typedef struct _VoipInputWidget VoipInputWidget;
+typedef struct _VoipInputWidgetClass VoipInputWidgetClass;
+
+struct _VoipInputWidgetClass {
+ GObjectClass parent_class;
+};
+
+struct _VoipInputWidget {
+ GObject parent;
+};
+
+GType voip_input_widget_get_type (void) G_GNUC_CONST;
+GtkWidget* voip_input_widget_new(DbusmenuMenuitem* twin_item);
+GtkWidget* voip_input_widget_get_ido_slider(VoipInputWidget* self);
+void voip_input_widget_update(VoipInputWidget* self, gdouble update);
+void voip_input_widget_tidy_up (GtkWidget *widget);
+
+G_END_DECLS
+
+#endif
+