diff options
Diffstat (limited to 'mixer/applet.c')
-rw-r--r-- | mixer/applet.c | 1484 |
1 files changed, 1484 insertions, 0 deletions
diff --git a/mixer/applet.c b/mixer/applet.c new file mode 100644 index 000000000..d517370ae --- /dev/null +++ b/mixer/applet.c @@ -0,0 +1,1484 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */ +/* GNOME Volume Applet + * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * applet.c: the main applet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* this is for lrint */ +#define _ISOC99_SOURCE +#include <math.h> +#include <string.h> + +#include <glib-object.h> +#include <gdk/gdkkeysyms.h> + +#include <gtk/gtk.h> + +#include <gconf/gconf-client.h> + +#include <libgnomeui/gnome-help.h> +#include <libgnome/gnome-desktop-item.h> + +#include "applet.h" +#include "keys.h" +#include "preferences.h" + +#define IS_PANEL_HORIZONTAL(o) \ + (o == PANEL_APPLET_ORIENT_UP || o == PANEL_APPLET_ORIENT_DOWN) + +static void gnome_volume_applet_class_init (GnomeVolumeAppletClass *klass); +static void gnome_volume_applet_init (GnomeVolumeApplet *applet); +static void gnome_volume_applet_dispose (GObject *object); + +static void gnome_volume_applet_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +static void gnome_volume_applet_popup_dock (GnomeVolumeApplet *applet); +static void gnome_volume_applet_popdown_dock (GnomeVolumeApplet *applet); + +static gboolean gnome_volume_applet_scroll (GtkWidget *widget, + GdkEventScroll *event); +static gboolean gnome_volume_applet_button (GtkWidget *widget, + GdkEventButton *event); +static gboolean gnome_volume_applet_key (GtkWidget *widget, + GdkEventKey *event); +static gdouble gnome_volume_applet_get_volume (GstMixer *mixer, + GstMixerTrack *track); + +static void gnome_volume_applet_background (PanelApplet *panel_applet, + PanelAppletBackgroundType type, + GdkColor *colour, + GdkPixmap *pixmap); +static void gnome_volume_applet_orientation (PanelApplet *applet, + PanelAppletOrient orient); + +static gboolean gnome_volume_applet_refresh (GnomeVolumeApplet *applet, + gboolean force_refresh, + gdouble volume, + gint mute); + +static void cb_notify_message (GstBus *bus, GstMessage *message, gpointer data); +static gboolean cb_check (gpointer data); + +static void cb_volume (GtkAdjustment *adj, + gpointer data); + +static void cb_gconf (GConfClient *client, + guint connection_id, + GConfEntry *entry, + gpointer data); + +static void cb_verb (BonoboUIComponent *uic, + gpointer data, + const gchar *verbname); + +static void cb_ui_event (BonoboUIComponent *comp, + const gchar *path, + Bonobo_UIComponent_EventType type, + const gchar *state_string, + gpointer data); +static void cb_theme_change (GtkIconTheme *icon_theme, + gpointer data); +static void cb_stop_scroll_events (GtkWidget *widget, + GdkEvent *event); + +static PanelAppletClass *parent_class = NULL; + + +G_DEFINE_TYPE (GnomeVolumeApplet, gnome_volume_applet, PANEL_TYPE_APPLET) + + +static void +init_pixbufs (GnomeVolumeApplet *applet) +{ + static const gchar *pix_filenames[] = { + "audio-volume-muted", + "audio-volume-low", + "audio-volume-medium", + "audio-volume-high", + NULL + }; + gint n; + + for (n = 0; pix_filenames[n] != NULL; n++) { + if (applet->pix[n]) + g_object_unref (applet->pix[n]); + + applet->pix[n] = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + pix_filenames[n], + applet->panel_size - 4, + 0, + NULL); + if (applet->pix[n] != NULL && + gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) { + GdkPixbuf *temp; + + temp = gdk_pixbuf_flip (applet->pix[n], TRUE); + g_object_unref (G_OBJECT (applet->pix[n])); + applet->pix[n] = temp; + } + } +} + +static void +gnome_volume_applet_class_init (GnomeVolumeAppletClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass); + PanelAppletClass *panelapplet_class = PANEL_APPLET_CLASS (klass); + + parent_class = g_type_class_ref (PANEL_TYPE_APPLET); + + gobject_class->dispose = gnome_volume_applet_dispose; + gtkwidget_class->key_press_event = gnome_volume_applet_key; + gtkwidget_class->button_press_event = gnome_volume_applet_button; + gtkwidget_class->scroll_event = gnome_volume_applet_scroll; + gtkwidget_class->size_allocate = gnome_volume_applet_size_allocate; + panelapplet_class->change_orient = gnome_volume_applet_orientation; + panelapplet_class->change_background = gnome_volume_applet_background; + + /* FIXME: + * - style-set. + */ +} + +static void +gnome_volume_applet_init (GnomeVolumeApplet *applet) +{ + GtkWidget *image; + AtkObject *ao; + + applet->timeout = 0; + applet->elements = NULL; + applet->client = gconf_client_get_default (); + applet->mixer = NULL; + applet->tracks = NULL; + applet->lock = FALSE; + applet->state = -1; + applet->prefs = NULL; + applet->dock = NULL; + applet->adjustment = NULL; + applet->panel_size = 24; + + g_set_application_name (_("Volume Applet")); + + /* init pixbufs */ + init_pixbufs (applet); + + /* icon (our main UI) */ + image = gtk_image_new (); + applet->image = GTK_IMAGE (image); + gtk_container_add (GTK_CONTAINER (applet), image); + gtk_widget_show (image); + gtk_window_set_default_icon_name ("multimedia-volume-control"); + + /* dock window (expanded UI) */ + applet->pop = FALSE; + + /* tooltip over applet */ + gtk_widget_set_tooltip_text (GTK_WIDGET (applet), _("Volume Control")); + + /* prevent scroll events from reaching the tooltip */ + g_signal_connect (G_OBJECT (applet), + "event-after", G_CALLBACK (cb_stop_scroll_events), + NULL); + + /* handle icon theme changes */ + g_signal_connect (gtk_icon_theme_get_default (), + "changed", G_CALLBACK (cb_theme_change), + applet); + + /* other stuff */ + panel_applet_add_preferences (PANEL_APPLET (applet), + "/schemas/apps/mixer_applet/prefs", + NULL); + panel_applet_set_flags (PANEL_APPLET (applet), + PANEL_APPLET_EXPAND_MINOR); + + /* i18n */ + ao = gtk_widget_get_accessible (GTK_WIDGET (applet)); + atk_object_set_name (ao, _("Volume Control")); + + /* Watch for signals from GST. */ + applet->bus = gst_bus_new (); + gst_bus_add_signal_watch (applet->bus); + g_signal_connect (G_OBJECT (applet->bus), "message::element", + (GCallback) cb_notify_message, applet); + +} + +/* Parse the list of tracks that are stored in GConf */ + +static char ** +parse_track_list (const char *track_list) +{ + if (track_list) + return g_strsplit (track_list, ":", 0); + else + return NULL; +} + +static GList * +select_tracks (GstElement *element, + const char *active_track_names, + gboolean reset_state) +{ + const GList *tracks, *l; + GstMixerTrack *track_fallback; + GList *active_tracks; + char **active_track_name_list; + + active_tracks = NULL; + track_fallback = NULL; + active_track_name_list = NULL; + + if (reset_state) { + gst_element_set_state (element, GST_STATE_READY); + if (gst_element_get_state(element, NULL, NULL, -1) != GST_STATE_CHANGE_SUCCESS) + return NULL; + } + + tracks = gst_mixer_list_tracks (GST_MIXER (element)); + if (active_track_names) + active_track_name_list = parse_track_list (active_track_names); + + for (l = tracks; l; l = l->next) { + GstMixerTrack *track = l->data; + gint i; + + if (!track->num_channels) + continue; + + if (!track_fallback) + track_fallback = track; + + if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_MASTER)) + track_fallback = track; + + if (active_track_name_list) { + for (i = 0; active_track_name_list[i] != NULL; i++) { + gchar *track_test = active_track_name_list[i]; + + if (!strcmp (track_test, track->label)) + active_tracks = g_list_append (active_tracks, track); + } + } + } + + /* if the list had no matches and we've got a fallback track, + * then use it. */ + + if (!active_tracks && track_fallback) + active_tracks = g_list_append (active_tracks, track_fallback); + + if (!active_tracks && reset_state) { + gst_element_set_state (element, GST_STATE_NULL); + } + + g_strfreev (active_track_name_list); + return active_tracks; +} + +static gboolean +select_element_and_track (GnomeVolumeApplet *applet, + GList *elements, + const char *active_element_name, + const char *active_track_names) +{ + GstElement *active_element; + GList *active_tracks, *l; + + applet->elements = elements; + + active_element = NULL; + active_tracks = NULL; + + if (active_element_name) { + for (l = elements; l; l = l->next) { + GstElement *element = l->data; + const char *element_name; + + element_name = g_object_get_data (G_OBJECT (element), + "gnome-volume-applet-name"); + + if (!strcmp (element_name, active_element_name)) { + active_element = element; + break; + } + } + } + + if (active_element) + active_tracks = select_tracks (active_element, active_track_names, TRUE); + + if (!active_tracks) { + active_element = NULL; + for (l = elements; l; l = l->next) { + GstElement *element = l->data; + + if ((active_tracks = select_tracks (element, active_track_names, TRUE))) { + active_element = element; + break; + } + } + } + + if (!active_element) + return FALSE; + + applet->mixer = g_object_ref (active_element); + gst_element_set_bus (GST_ELEMENT (applet->mixer), applet->bus); + applet->tracks = active_tracks; + g_list_foreach (applet->tracks, (GFunc) g_object_ref, NULL); + + return TRUE; +} + +static void +gnome_volume_applet_setup_timeout (GnomeVolumeApplet *applet) +{ + gboolean need_timeout = TRUE; + need_timeout = ((gst_mixer_get_mixer_flags (GST_MIXER (applet->mixer)) & + GST_MIXER_FLAG_AUTO_NOTIFICATIONS) == 0); + + if (need_timeout) { + if (applet->timeout == 0) { + applet->timeout = g_timeout_add (100, cb_check, applet); + } + } + else { + if (applet->timeout != 0) { + g_source_remove (applet->timeout); + applet->timeout = 0; + } + } +} + +gboolean +gnome_volume_applet_setup (GnomeVolumeApplet *applet, + GList *elements) +{ + GtkObject *adj; + BonoboUIComponent *component; + static const BonoboUIVerb verbs[] = { + BONOBO_UI_UNSAFE_VERB ("RunMixer", cb_verb), + BONOBO_UI_UNSAFE_VERB ("Mute", cb_verb), + BONOBO_UI_UNSAFE_VERB ("Help", cb_verb), + BONOBO_UI_UNSAFE_VERB ("About", cb_verb), + BONOBO_UI_UNSAFE_VERB ("Pref", cb_verb), + BONOBO_UI_VERB_END + }; + gchar *key; + gchar *active_element_name; + gchar *active_track_name; + GstMixerTrack *first_track; + gboolean res; + + active_element_name = panel_applet_gconf_get_string (PANEL_APPLET (applet), + GNOME_VOLUME_APPLET_KEY_ACTIVE_ELEMENT, + NULL); + + active_track_name = panel_applet_gconf_get_string (PANEL_APPLET (applet), + GNOME_VOLUME_APPLET_KEY_ACTIVE_TRACK, + NULL); + + res = select_element_and_track (applet, elements, active_element_name, + active_track_name); + g_free (active_element_name); + g_free (active_track_name); + + if (res) { + first_track = g_list_first (applet->tracks)->data; + + applet->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (50, 0, 100, + 4, 10, 0)); + /* We want a reference from the applet as well as from the dock it + * will be attached to. */ + g_object_ref_sink (applet->adjustment); + g_signal_connect (applet->adjustment, "value-changed", + G_CALLBACK (cb_volume), applet); + + gtk_adjustment_set_value (applet->adjustment, + gnome_volume_applet_get_volume (applet->mixer, + first_track)); + } + + gnome_volume_applet_orientation (PANEL_APPLET (applet), + panel_applet_get_orient (PANEL_APPLET (applet))); + + /* menu - done here because bonobo is intialized now */ + panel_applet_setup_menu_from_file (PANEL_APPLET (applet), + DATADIR, + "GNOME_MixerApplet.xml", + NULL, verbs, applet); + component = panel_applet_get_popup_component (PANEL_APPLET (applet)); + g_signal_connect (component, "ui-event", G_CALLBACK (cb_ui_event), applet); + + gnome_volume_applet_refresh (applet, TRUE, -1, -1); + if (res) { + gnome_volume_applet_setup_timeout (applet); + + /* gconf */ + key = panel_applet_gconf_get_full_key (PANEL_APPLET (applet), + GNOME_VOLUME_APPLET_KEY_ACTIVE_ELEMENT); + gconf_client_notify_add (applet->client, key, + cb_gconf, applet, NULL, NULL); + g_free (key); + key = panel_applet_gconf_get_full_key (PANEL_APPLET (applet), + GNOME_VOLUME_APPLET_KEY_ACTIVE_TRACK); + gconf_client_notify_add (applet->client, key, + cb_gconf, applet, NULL, NULL); + g_free (key); + } + + gtk_widget_show (GTK_WIDGET (applet)); + + return TRUE; +} + +static void +gnome_volume_applet_dispose (GObject *object) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (object); + gint n; + + gnome_volume_applet_popdown_dock (applet); + + if (applet->elements) { + GList *item; + + for (item = applet->elements; item != NULL; item = item->next) { + GstElement *element = GST_ELEMENT (item->data); + + gst_element_set_state (element, GST_STATE_NULL); + gst_object_unref (GST_OBJECT (element)); + } + g_list_free (applet->elements); + applet->elements = NULL; + } + + if (applet->tracks) { + g_list_foreach (applet->tracks, (GFunc) g_object_unref, NULL); + g_list_free (applet->tracks); + applet->tracks = NULL; + } + + if (applet->mixer) { + gst_object_unref (GST_OBJECT (applet->mixer)); + applet->mixer = NULL; + } + + if (applet->timeout) { + g_source_remove (applet->timeout); + applet->timeout = 0; + } + + if (applet->dock) { + g_object_unref (applet->dock); + applet->dock = NULL; + } + + if (applet->adjustment) { + g_object_unref (applet->adjustment); + applet->adjustment = NULL; + } + + for (n = 0; n < 5; n++) { + if (applet->pix[n] != NULL) { + g_object_unref (G_OBJECT (applet->pix[n])); + applet->pix[n] = NULL; + } + } + + if (applet->bus) { + gst_bus_remove_signal_watch (applet->bus); + gst_object_unref (applet->bus); + applet->bus = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/* + * Show a dialog (once) when no mixer is available. + */ + +static void +show_no_mixer_dialog (GnomeVolumeApplet *applet) +{ + static gboolean shown = FALSE; + GtkWidget *dialog; + + if (shown) + return; + shown = TRUE; + + dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, "%s\n\n%s", + _("The volume control did not find any elements and/or " + "devices to control. This means either that you don't " + "have the right GStreamer plugins installed, or that you " + "don't have a sound card configured."), + _("You can remove the volume control from the panel by " + "right-clicking the speaker icon on the panel and " + "selecting \"Remove From Panel\" from the menu.")); + gtk_widget_show (dialog); + g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); +} + +/* + * get position that the dock should get based on applet position. + */ + +static void +gnome_volume_applet_get_dock_position (GnomeVolumeApplet *applet, + gint *_x, gint *_y) +{ + GtkWidget *widget = GTK_WIDGET (applet); + gint x, y; + + gdk_window_get_origin (widget->window, &x, &y); + switch (panel_applet_get_orient (PANEL_APPLET (applet))) { + case PANEL_APPLET_ORIENT_DOWN: + x += widget->allocation.x; + x -= (GTK_WIDGET (applet->dock)->allocation.width - + widget->allocation.width) / 2; + y += widget->allocation.height + widget->allocation.y; + break; + case PANEL_APPLET_ORIENT_UP: + x += widget->allocation.x; + x -= (GTK_WIDGET (applet->dock)->allocation.width - + widget->allocation.width) / 2; + y += widget->allocation.y; + y -= GTK_WIDGET (applet->dock)->allocation.height; + break; + case PANEL_APPLET_ORIENT_RIGHT: + x += widget->allocation.width + widget->allocation.x; + y += widget->allocation.y; + y -= (GTK_WIDGET (applet->dock)->allocation.height - + widget->allocation.height) / 2; + break; + case PANEL_APPLET_ORIENT_LEFT: + x += widget->allocation.x; + x -= GTK_WIDGET (applet->dock)->allocation.width; + y += widget->allocation.y; + y -= (GTK_WIDGET (applet->dock)->allocation.height - + widget->allocation.height) / 2; + break; + default: + g_assert_not_reached (); + } + + *_x = x; + *_y = y; +} + +/* + * popup (show) or popdown (hide) the dock. + */ + +static void +gnome_volume_applet_popup_dock (GnomeVolumeApplet *applet) +{ + GtkWidget *widget = GTK_WIDGET (applet); + gint x, y; + + /* Get it in just about the right position so that it + * doesn't flicker to obviously when we reposition it. */ + gnome_volume_applet_get_dock_position (applet, &x, &y); + gtk_window_move (GTK_WINDOW (applet->dock), x, y); + + gtk_widget_show_all (GTK_WIDGET (applet->dock)); + + /* Reposition the window now that we know its actual size + * and can center it. */ + gnome_volume_applet_get_dock_position (applet, &x, &y); + gtk_window_move (GTK_WINDOW (applet->dock), x, y); + + /* Set the keyboard focus in the correct place. */ + gnome_volume_applet_dock_set_focus (applet->dock); + + /* set menu item as active */ + gtk_widget_set_state (GTK_WIDGET (applet), GTK_STATE_SELECTED); + + /* keep state */ + applet->pop = TRUE; +} + +static void +gnome_volume_applet_popdown_dock (GnomeVolumeApplet *applet) +{ + GtkWidget *widget = GTK_WIDGET (applet); + + if (!applet->pop) + return; + + /* hide */ + gtk_widget_hide_all (GTK_WIDGET (applet->dock)); + + /* set menu item as active */ + gtk_widget_set_state (GTK_WIDGET (applet), GTK_STATE_NORMAL); + + /* keep state */ + applet->pop = FALSE; +} + +static void +gnome_volume_applet_pop_dock (GnomeVolumeApplet *applet) +{ + if (applet->pop) { + gnome_volume_applet_popdown_dock (applet); + } else { + gnome_volume_applet_popup_dock (applet); + } +} + +gboolean +mixer_is_muted (GnomeVolumeApplet *applet) +{ + return applet->state & 1; +} + +/* + * Toggle mute. + */ + +void +gnome_volume_applet_toggle_mute (GnomeVolumeApplet *applet) +{ + BonoboUIComponent *component; + gboolean mute = mixer_is_muted (applet); + gboolean newmute = !mute; + GList *tracks; + + for (tracks = g_list_first (applet->tracks); tracks; tracks = tracks->next) + gst_mixer_set_mute (applet->mixer, tracks->data, !mute); + + if (mute) { + /* sync back actual volume */ + cb_volume (applet->adjustment, applet); + } + + /* update component */ + component = panel_applet_get_popup_component (PANEL_APPLET (applet)); + bonobo_ui_component_set_prop (component, + "/commands/Mute", + "state", newmute ? "1" : "0", NULL); + + /* Update the dock. */ + if (applet->dock) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (applet->dock->mute), + newmute); + } + + /* update graphic - this should happen automagically after the next + * idle call, but apparently doesn't for some people... */ + gnome_volume_applet_refresh (applet, TRUE, -1, newmute); +} + +/* + * Run g-v-c. + */ + +void +gnome_volume_applet_run_mixer (GnomeVolumeApplet *applet) +{ + GnomeDesktopItem *ditem; + GError *error = NULL; + + if ((ditem = gnome_desktop_item_new_from_basename ("gnome-volume-control.desktop", 0, NULL))) { + gnome_desktop_item_set_launch_time (ditem, gtk_get_current_event_time ()); + gnome_desktop_item_launch_on_screen (ditem, NULL, + GNOME_DESKTOP_ITEM_LAUNCH_ONLY_ONE, + gtk_widget_get_screen (GTK_WIDGET (applet)), + -1, &error); + gnome_desktop_item_unref (ditem); + } + else { + gdk_spawn_command_line_on_screen ( + gtk_widget_get_screen (GTK_WIDGET (applet)), + "gnome-volume-control", &error); + } + + if (error) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Failed to start Volume Control: %s"), + error->message); + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), NULL); + gtk_widget_show (dialog); + g_error_free (error); + } +} + +/* + * Control events, change volume and so on. + */ + +static gboolean +gnome_volume_applet_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (widget); + + if (!applet->mixer) { + show_no_mixer_dialog (applet); + return TRUE; + } + + if (event->type == GDK_SCROLL) { + switch (event->direction) { + case GDK_SCROLL_UP: + case GDK_SCROLL_DOWN: { + gdouble volume = gtk_adjustment_get_value (applet->adjustment); + + if (event->direction == GDK_SCROLL_UP) { + volume += applet->adjustment->step_increment; + if (volume > applet->adjustment->upper) + volume = applet->adjustment->upper; + } else { + volume -= applet->adjustment->step_increment; + if (volume < applet->adjustment->lower) + volume = applet->adjustment->lower; + } + + gtk_adjustment_set_value (applet->adjustment, volume); + return TRUE; + } + default: + break; + } + } + + if (GTK_WIDGET_CLASS (parent_class)->scroll_event) + return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event); + else + return FALSE; +} + +static gboolean +gnome_volume_applet_button (GtkWidget *widget, + GdkEventButton *event) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (widget); + + if (event->window != GTK_WIDGET (applet)->window && + event->type == GDK_BUTTON_PRESS) { + gnome_volume_applet_popdown_dock (applet); + return TRUE; + } else if (event->window == GTK_WIDGET (applet)->window) { + switch (event->button) { + case 1: + switch (event->type) { + case GDK_BUTTON_PRESS: + if (!applet->mixer) { + show_no_mixer_dialog (applet); + } else { + gnome_volume_applet_pop_dock (applet); + } + return TRUE; + case GDK_2BUTTON_PRESS: + if (applet->mixer) { + gnome_volume_applet_popdown_dock (applet); + } + gnome_volume_applet_run_mixer (applet); + return TRUE; + default: + break; + } + break; + case 2: /* move */ + case 3: /* menu */ + if (applet->pop) { + gnome_volume_applet_popdown_dock (applet); + return TRUE; + } + break; + default: + break; + } + } + + if (GTK_WIDGET_CLASS (parent_class)->button_press_event) + return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); + + return FALSE; +} + +static gboolean +gnome_volume_applet_key (GtkWidget *widget, + GdkEventKey *event) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (widget); + + if (!applet->mixer) { + show_no_mixer_dialog (applet); + } else switch (event->keyval) { + case GDK_KP_Enter: + case GDK_ISO_Enter: + case GDK_3270_Enter: + case GDK_Return: + case GDK_space: + case GDK_KP_Space: + gnome_volume_applet_pop_dock (applet); + return TRUE; + case GDK_m: + if (event->state == GDK_CONTROL_MASK) { + gnome_volume_applet_toggle_mute (applet); + return TRUE; + } + break; + case GDK_o: + if (event->state == GDK_CONTROL_MASK) { + gnome_volume_applet_run_mixer (applet); + return TRUE; + } + break; + case GDK_Escape: + gnome_volume_applet_popdown_dock (applet); + return TRUE; + case GDK_Page_Up: + case GDK_Page_Down: + case GDK_Left: + case GDK_Right: + case GDK_Up: + case GDK_Down: { + gdouble volume = gtk_adjustment_get_value (applet->adjustment); + gdouble increment; + + if (event->state != 0) + break; + + if (event->keyval == GDK_Up || event->keyval == GDK_Down + ||event->keyval == GDK_Left) + increment = applet->adjustment->step_increment; + else + increment = applet->adjustment->page_increment; + + if (event->keyval == GDK_Page_Up || event->keyval == GDK_Up + ||event->keyval == GDK_Right) { + volume += increment; + if (volume > applet->adjustment->upper) + volume = applet->adjustment->upper; + } else { + volume -= increment; + if (volume < applet->adjustment->lower) + volume = applet->adjustment->lower; + } + + gtk_adjustment_set_value (applet->adjustment, volume); + return TRUE; + } + default: + break; + } + + return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event); +} + +/* + * Change orientation or size of panel. + */ + +static void +gnome_volume_applet_orientation (PanelApplet *_applet, + PanelAppletOrient orientation) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (_applet); + GtkWidget *dock; + + if (applet->dock) { + gtk_widget_destroy (GTK_WIDGET (applet->dock)); + } + dock = gnome_volume_applet_dock_new (IS_PANEL_HORIZONTAL (orientation) ? + GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, + applet); + applet->dock = GNOME_VOLUME_APPLET_DOCK (dock); + gnome_volume_applet_dock_change (applet->dock, applet->adjustment); + + if (PANEL_APPLET_CLASS (parent_class)->change_orient) + PANEL_APPLET_CLASS (parent_class)->change_orient (_applet, orientation); +} + +void gnome_volume_applet_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (widget); + PanelAppletOrient orient; + + if (GTK_WIDGET_CLASS (parent_class)->size_allocate) + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + orient = panel_applet_get_orient (PANEL_APPLET (applet)); + + if (orient == PANEL_APPLET_ORIENT_UP || orient == PANEL_APPLET_ORIENT_DOWN) { + if (applet->panel_size == allocation->height) + return; + applet->panel_size = allocation->height; + } + else { + if (applet->panel_size == allocation->width) + return; + applet->panel_size = allocation->width; + } + + init_pixbufs (applet); + gnome_volume_applet_refresh (applet, TRUE, -1, -1); +} + +static void +gnome_volume_applet_background (PanelApplet *_applet, + PanelAppletBackgroundType type, + GdkColor *colour, + GdkPixmap *pixmap) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (_applet); + GtkRcStyle *rc_style; + GtkStyle *style; + + /* reset style */ + gtk_widget_set_style (GTK_WIDGET (applet), NULL); + rc_style = gtk_rc_style_new (); + gtk_widget_modify_style (GTK_WIDGET (applet), rc_style); + gtk_rc_style_unref (rc_style); + + switch (type) { + case PANEL_NO_BACKGROUND: + break; + case PANEL_COLOR_BACKGROUND: + gtk_widget_modify_bg (GTK_WIDGET (applet), + GTK_STATE_NORMAL, colour); + break; + case PANEL_PIXMAP_BACKGROUND: + style = gtk_style_copy (GTK_WIDGET (applet)->style); + if (style->bg_pixmap[GTK_STATE_NORMAL]) + g_object_unref (style->bg_pixmap[GTK_STATE_NORMAL]); + style->bg_pixmap[GTK_STATE_NORMAL] = g_object_ref (pixmap); + gtk_widget_set_style (GTK_WIDGET (applet), style); + g_object_unref (style); + break; + } +} + +/* + * This needs to be here because not all tracks have the same volume range, + * so you can send this function the track and a new volume and it will be + * scaled according to the volume range of the track in question. + */ + +void +gnome_volume_applet_adjust_volume (GstMixer *mixer, + GstMixerTrack *track, + gdouble volume) +{ + int range = track->max_volume - track->min_volume; + gdouble scale = ((gdouble) range) / 100; + int *volumes, n, volint; + + if (volume == 1.0) { + volint = track->max_volume; + } else if (volume == 0.0) { + volint = track->min_volume; + } else { + volume *= scale; + volume += track->min_volume; + volint = lrint (volume); + } + + volumes = g_new (gint, track->num_channels); + for (n = 0; n < track->num_channels; n++) + volumes[n] = volint; + gst_mixer_set_volume (mixer, track, volumes); + + g_free (volumes); +} + +static gdouble +gnome_volume_applet_get_volume (GstMixer *mixer, + GstMixerTrack *track) +{ + int *volumes, n; + gdouble j; + + if (!track || !mixer) + return -1; + + volumes = g_new (gint, track->num_channels); + gst_mixer_get_volume (mixer, track, volumes); + + j = 0; + for (n = 0; n < track->num_channels; n++) + j += volumes[n]; + g_free (volumes); + j /= track->num_channels; + + return 100 * (j - track->min_volume) / (track->max_volume - track->min_volume); +} + +/* + * Volume changed. + */ + +static void +cb_volume (GtkAdjustment *adj, + gpointer data) +{ + GnomeVolumeApplet *applet = data; + gdouble v; + GList *iter; + + if (applet->lock) + return; + applet->lock = TRUE; + + v = gtk_adjustment_get_value (adj); + + for (iter = g_list_first (applet->tracks); iter; iter = iter->next) { + GstMixerTrack *track = iter->data; + gnome_volume_applet_adjust_volume (applet->mixer, track, v); + } + + applet->lock = FALSE; + applet->force_next_update = TRUE; + gnome_volume_applet_refresh (GNOME_VOLUME_APPLET (data), FALSE, v, -1); +} + +/* + * Automatic timer. Check for changes. + */ + +#define STATE(vol,m) (((gint) vol << 1) | (m ? 1 : 0)) + +static gboolean +gnome_volume_applet_refresh (GnomeVolumeApplet *applet, + gboolean force_refresh, + gdouble volume, + gint mute) +{ + BonoboUIComponent *component; + GdkPixbuf *pixbuf; + gint n; + gboolean show_mute, did_change; + gchar *tooltip_str; + GstMixerTrack *first_track; + GString *track_names; + GList *iter; + + show_mute = 0; + + if (!applet->mixer) { + n = 0; + show_mute = 0; + mute = 0; + } else if (!applet->tracks) { + return FALSE; + } else { + first_track = g_list_first (applet->tracks)->data; + if (volume == -1) { + /* only first track */ + volume = gnome_volume_applet_get_volume (applet->mixer, first_track); + } + if (mute == -1) { + mute = GST_MIXER_TRACK_HAS_FLAG (first_track, + GST_MIXER_TRACK_MUTE) ? 1 : 0; + } + if (volume <= 0 || mute) { + show_mute = 1; + n = 0; + } + else { + /* select image */ + n = 3 * volume / 100 + 1; + if (n < 1) + n = 1; + if (n > 3) + n = 3; + } + } + + did_change = (force_refresh || (STATE (volume, mute) != applet->state) || + applet->force_next_update); + applet->force_next_update = FALSE; + + if (did_change) { + if (show_mute) { + pixbuf = applet->pix[0]; + } else { + pixbuf = applet->pix[n]; + } + + gtk_image_set_from_pixbuf (applet->image, pixbuf); + applet->state = STATE (volume, mute); + } + + if (!did_change || !applet->mixer) + return did_change; + + /* build names of selecter tracks */ + track_names = g_string_new (""); + for (iter = g_list_first (applet->tracks); iter; iter = iter->next) { + GstMixerTrack *track = iter->data; + + if (iter->next != NULL) + g_string_append_printf (track_names, "%s / ", track->label); + else + track_names = g_string_append (track_names, track->label); + } + + if (show_mute) { + tooltip_str = g_strdup_printf (_("%s: muted"), track_names->str); + } else { + /* Translator comment: I'm not all too sure if this makes sense + * to mark as a translation, but anyway. The string is a list of + * selected tracks, the number is the volume in percent. You + * most likely want to keep this as-is. */ + tooltip_str = g_strdup_printf (_("%s: %d%%"), track_names->str, + (int) volume); + } + g_string_free (track_names, TRUE); + + gtk_widget_set_tooltip_text (GTK_WIDGET (applet), tooltip_str); + g_free (tooltip_str); + + applet->lock = TRUE; + if (volume != 0) { + gtk_adjustment_set_value (applet->adjustment, volume); + } + applet->lock = FALSE; + + /* update mute status (bonobo) */ + component = panel_applet_get_popup_component (PANEL_APPLET (applet)); + bonobo_ui_component_set_prop (component, + "/commands/Mute", + "state", mute ? "1" : "0", NULL); + return did_change; +} + +static void +cb_notify_message (GstBus *bus, GstMessage *message, gpointer data) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (data); + GstMixerMessageType type; + GstMixerTrack *first_track; + GstMixerTrack *track = NULL; + gint mute; + gdouble volume; + + if (applet->tracks == NULL || + GST_MESSAGE_SRC (message) != GST_OBJECT (applet->mixer)) { + /* No tracks, or not from our mixer - can't update anything anyway */ + return; + } + + volume = mute = -1; + + first_track = g_list_first (applet->tracks)->data; + + /* This code only calls refresh if the first_track changes, because the + * refresh code only retrieves the current value from that track anyway */ + type = gst_mixer_message_get_type (message); + if (type == GST_MIXER_MESSAGE_MUTE_TOGGLED) { + gboolean muted; + gst_mixer_message_parse_mute_toggled (message, &track, &muted); + mute = muted ? 1 : 0; + } + else if (type == GST_MIXER_MESSAGE_VOLUME_CHANGED) { + gint n, num_channels, *vols; + volume = 0.0; + + gst_mixer_message_parse_volume_changed (message, &track, &vols, &num_channels); + for (n = 0; n < num_channels; n++) + volume += vols[n]; + volume /= track->num_channels; + volume = 100 * volume / (track->max_volume - track->min_volume); + } else + { + return; + } + + if (first_track == track) + gnome_volume_applet_refresh (GNOME_VOLUME_APPLET (data), FALSE, volume, mute); +} + +static gboolean +cb_check (gpointer data) +{ + static int time_counter = -1; + static int timeout = 15; + static gboolean recent_change = FALSE; + gboolean did_change; + + time_counter++; + + /* + * This timeout is called 10 times per second. Only do the update every + * 15 times this function is called (every 1.5 seconds), unless the value + * actually changed. + */ + if (time_counter % timeout == 0 || recent_change) { + did_change = gnome_volume_applet_refresh (GNOME_VOLUME_APPLET (data), + FALSE, -1, -1); + + /* + * If a change was done, set recent_change so that the update is + * done 10 times a second for 10 seconds and reset the counter to 0. + * This way we update frequently for 10 seconds after the last time + * the value is actually changed. + */ + if (did_change) { + recent_change = TRUE; + time_counter = 0; + timeout = 100; + } else if (time_counter % timeout == 0) { + /* + * When the counter gets to the timeout, reset recent_change and + * time_counter so we go back to the behavior where we only check + * every 15 times the function is called. + */ + recent_change = FALSE; + time_counter = 0; + timeout = 15; + } + } + + return TRUE; +} + +/* + * GConf callback. + */ + +static void +cb_gconf (GConfClient *client, + guint connection_id, + GConfEntry *entry, + gpointer data) +{ + GnomeVolumeApplet *applet = data; + GConfValue *value; + const gchar *str, *key; + const GList *item; + gboolean newdevice = FALSE; + gchar *keyroot; + GList *active_tracks; + + active_tracks = NULL; + + keyroot = panel_applet_gconf_get_full_key (PANEL_APPLET (applet), ""); + key = gconf_entry_get_key (entry); + if (strncmp (key, keyroot, strlen (keyroot))) { + g_free (keyroot); + return; + } + key += strlen (keyroot); + g_free (keyroot); + + if ((value = gconf_entry_get_value (entry)) != NULL && + (value->type == GCONF_VALUE_STRING) && + (str = gconf_value_get_string (value)) != NULL) { + if (!strcmp (key, GNOME_VOLUME_APPLET_KEY_ACTIVE_ELEMENT)) { + for (item = applet->elements; item != NULL; item = item->next) { + gchar *cur_el_str = g_object_get_data (item->data, + "gnome-volume-applet-name"); + + if (!strcmp (cur_el_str, str)) { + GstElement *old_element = GST_ELEMENT (applet->mixer), + *new_element = item->data; + + if (new_element != old_element) { + /* change element */ + gst_element_set_state (item->data, GST_STATE_READY); + if (gst_element_get_state (item->data, NULL, NULL, -1) != GST_STATE_CHANGE_SUCCESS) + continue; + + /* save */ + gst_object_replace ((GstObject **) &applet->mixer, item->data); + gst_element_set_state (old_element, GST_STATE_NULL); + gnome_volume_applet_setup_timeout (applet); + newdevice = TRUE; + } + break; + } + } + } + + if (!strcmp (key, GNOME_VOLUME_APPLET_KEY_ACTIVE_TRACK) || newdevice) { + if (!active_tracks) { + active_tracks = select_tracks (GST_ELEMENT (applet->mixer), str, FALSE); + } + + if (active_tracks) { + GstMixerTrack *first_track; + + /* copy the newly created track list over to the main list */ + g_list_free (applet->tracks); + applet->tracks = g_list_copy (active_tracks); + + first_track = g_list_first (active_tracks)->data; + + /* dock */ + gtk_adjustment_set_value (applet->adjustment, + gnome_volume_applet_get_volume (applet->mixer, + first_track)); + + /* if preferences window is open, update */ + if (applet->prefs) { + gnome_volume_applet_preferences_change ( + GNOME_VOLUME_APPLET_PREFERENCES (applet->prefs), + applet->mixer, applet->tracks); + } + + applet->force_next_update = TRUE; + } + } + } +} + +/* + * bonobo verb callback. + */ + +static void +cb_prefs_destroy (GtkWidget *widget, + gpointer data) +{ + GNOME_VOLUME_APPLET (data)->prefs = NULL; +} + +static void +cb_verb (BonoboUIComponent *uic, + gpointer data, + const gchar *verbname) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (data); + + if (!strcmp (verbname, "RunMixer")) { + gnome_volume_applet_run_mixer (applet); + } else if (!strcmp (verbname, "Help")) { + GError *error = NULL; + + gnome_help_display_on_screen ("mixer_applet2", NULL, + gtk_widget_get_screen (GTK_WIDGET (applet)), + &error); + + if (error) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Failed to display help: %s"), + error->message); + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), NULL); + gtk_widget_show (dialog); + g_error_free (error); + } + } else if (!strcmp (verbname, "About")) { + + const gchar *authors[] = { "Ronald Bultje <rbultje@ronald.bitfreak.net>", + NULL }; + + char *comments = g_strdup_printf ("%s\n\n%s", + _("Volume control for your GNOME Panel."), + _("Using GStreamer 0.10.") + ); + + gtk_show_about_dialog (NULL, + "version", VERSION, + "copyright", "\xC2\xA9 2004 Ronald Bultje", + "comments", comments, + "authors", authors, + "translator-credits", _("translator-credits"), + "logo-icon-name", "multimedia-volume-control", + NULL); + + g_free (comments); + + } else if (!strcmp (verbname, "Pref")) { + if (!applet->mixer) { + show_no_mixer_dialog (applet); + } else { + if (applet->prefs) + return; + + applet->prefs = gnome_volume_applet_preferences_new (PANEL_APPLET (applet), + applet->elements, + applet->mixer, + applet->tracks); + g_signal_connect (applet->prefs, "destroy", + G_CALLBACK (cb_prefs_destroy), applet); + gtk_widget_show (applet->prefs); + } + } else { + g_warning ("Unknown bonobo command '%s'", verbname); + } +} + +static void +cb_ui_event (BonoboUIComponent *comp, + const gchar *verbname, + Bonobo_UIComponent_EventType type, + const gchar *state_string, + gpointer data) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (data); + + if (!applet->mixer) { + show_no_mixer_dialog (applet); + } else if (!strcmp (verbname, "Mute")) { + /* mute will have a value of 4 without the ? TRUE : FALSE bit... */ + gboolean mute = applet->state & 1, + want_mute = !strcmp (state_string, "1") ? TRUE : FALSE; + + if (mute != want_mute) + gnome_volume_applet_toggle_mute (applet); + } else { + g_warning ("Unknown bonobo command '%s'", verbname); + } +} + +static void +cb_theme_change (GtkIconTheme *icon_theme, + gpointer data) +{ + GnomeVolumeApplet *applet = GNOME_VOLUME_APPLET (data); + + init_pixbufs (applet); + gnome_volume_applet_refresh (applet, TRUE, -1, -1); +} + +/* + * Block the tooltips event-after handler on scroll events. + */ + +static void +cb_stop_scroll_events (GtkWidget *widget, + GdkEvent *event) +{ + if (event->type == GDK_SCROLL) + g_signal_stop_emission_by_name (widget, "event-after"); +} + |