summaryrefslogtreecommitdiff
path: root/mixer/applet.c
diff options
context:
space:
mode:
Diffstat (limited to 'mixer/applet.c')
-rw-r--r--mixer/applet.c1484
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");
+}
+