diff options
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/active-sink.c | 66 | ||||
-rw-r--r-- | src/active-sink.h | 25 | ||||
-rw-r--r-- | src/common-defs.h | 6 | ||||
-rw-r--r-- | src/indicator-sound.c | 66 | ||||
-rw-r--r-- | src/music-player-bridge.vala | 8 | ||||
-rw-r--r-- | src/mute-menu-item.c | 1 | ||||
-rw-r--r-- | src/player-item.vala | 3 | ||||
-rw-r--r-- | src/pulseaudio-mgr.c | 209 | ||||
-rw-r--r-- | src/pulseaudio-mgr.h | 2 | ||||
-rw-r--r-- | src/settings-manager.vala | 2 | ||||
-rw-r--r-- | src/sound-service-dbus.c | 8 | ||||
-rw-r--r-- | src/sound-service-dbus.h | 3 | ||||
-rw-r--r-- | src/sound-service.c | 2 | ||||
-rw-r--r-- | src/voip-input-menu-item.c | 277 | ||||
-rw-r--r-- | src/voip-input-menu-item.h | 70 | ||||
-rw-r--r-- | src/voip-input-widget.c | 279 | ||||
-rw-r--r-- | src/voip-input-widget.h | 55 |
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 + |