Update copyrights everywhere.
[lxde/lxpanel.git] / src / plugins / volumealsa / volumealsa.c
index 63b5fb4..ffc932e 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2008 LxDE Developers, see the file AUTHORS for details.
+ * Copyright (c) 2008-2014 LxDE Developers, see the file AUTHORS for details.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <alsa/asoundlib.h>
 #include <poll.h>
-#include "panel.h"
-#include "misc.h"
+#include <libfm/fm-gtk.h>
 #include "plugin.h"
 #include "dbg.h"
 
-#define ICONS_VOLUME PACKAGE_DATA_DIR "/lxpanel/images/volume.png"
-#define ICONS_MUTE PACKAGE_DATA_DIR "/lxpanel/images/mute.png"
+#define ICONS_VOLUME_HIGH   PACKAGE_DATA_DIR "/images/volume-high.png"
+#define ICONS_VOLUME_MEDIUM PACKAGE_DATA_DIR "/images/volume-medium.png"
+#define ICONS_VOLUME_LOW    PACKAGE_DATA_DIR "/images/volume-low.png"
+#define ICONS_MUTE          PACKAGE_DATA_DIR "/images/mute.png"
 
 typedef struct {
 
     /* Graphics. */
-    Plugin * plugin;                           /* Back pointer to plugin */
+    GtkWidget * plugin;                                /* Back pointer to the widget */
+    LXPanel * panel;                           /* Back pointer to panel */
     GtkWidget * tray_icon;                     /* Displayed image */
     GtkWidget * popup_window;                  /* Top level window for popup */
     GtkWidget * volume_scale;                  /* Scale for volume */
@@ -50,26 +52,24 @@ typedef struct {
     snd_mixer_selem_id_t * sid;                        /* The element ID */
     snd_mixer_elem_t * master_element;         /* The Master element */
     guint mixer_evt_idle;                      /* Timer to handle restarting poll */
+    guint restart_idle;
+
+    /* unloading and error handling */
+    GIOChannel **channels;                      /* Channels that we listen to */
+    guint num_channels;                         /* Number of channels */
+
+    /* Icons */
+    const char* icon;
+    const char* icon_panel;
+    const char* icon_fallback;
+
 } VolumeALSAPlugin;
 
-static gboolean asound_find_element(VolumeALSAPlugin * vol, const char * ename);
-static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol);
-static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer);
+static gboolean asound_restart(gpointer vol_gpointer);
 static gboolean asound_initialize(VolumeALSAPlugin * vol);
-static gboolean asound_has_mute(VolumeALSAPlugin * vol);
-static gboolean asound_is_muted(VolumeALSAPlugin * vol);
-static int asound_get_volume(VolumeALSAPlugin * vol);
-static void asound_set_volume(VolumeALSAPlugin * vol, int volume);
+static void asound_deinitialize(VolumeALSAPlugin * vol);
 static void volumealsa_update_display(VolumeALSAPlugin * vol);
-static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, VolumeALSAPlugin * vol);
-static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event, VolumeALSAPlugin * vol);
-static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol);
-static void volumealsa_popup_scale_scrolled(GtkScale * scale, GdkEventScroll * evt, VolumeALSAPlugin * vol);
-static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin * vol);
-static void volumealsa_build_popup_window(Plugin * p);
-static int volumealsa_constructor(Plugin * p, char ** fp);
-static void volumealsa_destructor(Plugin * p);
-static void volumealsa_panel_configuration_changed(Plugin * p);
+static void volumealsa_destructor(gpointer user_data);
 
 /*** ALSA ***/
 
@@ -111,7 +111,8 @@ static gboolean asound_find_element(VolumeALSAPlugin * vol, const char * ename)
 
 static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol)
 {
-    vol->mixer_evt_idle = 0;
+    if (!g_source_is_destroyed(g_main_current_source()))
+        vol->mixer_evt_idle = 0;
     return FALSE;
 }
 
@@ -119,11 +120,12 @@ static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol)
 static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer)
 {
     VolumeALSAPlugin * vol = (VolumeALSAPlugin *) vol_gpointer;
+    int res = 0;
 
     if (vol->mixer_evt_idle == 0)
     {
         vol->mixer_evt_idle = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) asound_reset_mixer_evt_idle, vol, NULL);
-        snd_mixer_handle_events(vol->mixer);
+        res = snd_mixer_handle_events(vol->mixer);
     }
 
     if (cond & G_IO_IN)
@@ -132,15 +134,45 @@ static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpoi
         volumealsa_update_display(vol);
     }
 
-    if (cond & G_IO_HUP)
+    if ((cond & G_IO_HUP) || (res < 0))
     {
         /* This means there're some problems with alsa. */
+        ERR("volumealsa: ALSA (or pulseaudio) had a problem: \n"
+                "volumealsa: snd_mixer_handle_events() = %d,"
+                " cond 0x%x (IN: 0x%x, HUP: 0x%x).\n", res, cond,
+                G_IO_IN, G_IO_HUP);
+        gtk_widget_set_tooltip_text(vol->plugin, "ALSA (or pulseaudio) had a problem."
+                " Please check the lxpanel logs.");
+
+        if (vol->restart_idle == 0)
+            vol->restart_idle = g_timeout_add_seconds(1, asound_restart, vol);
+
         return FALSE;
     }
 
     return TRUE;
 }
 
+static gboolean asound_restart(gpointer vol_gpointer)
+{
+    VolumeALSAPlugin * vol = vol_gpointer;
+
+    if (g_source_is_destroyed(g_main_current_source()))
+        return FALSE;
+
+    asound_deinitialize(vol);
+
+    if (!asound_initialize(vol)) {
+        ERR("volumealsa: Re-initialization failed.\n");
+        return TRUE; // try again in a second
+    }
+
+    ERR("volumealsa: Restarted ALSA interface...\n");
+
+    vol->restart_idle = 0;
+    return FALSE;
+}
+
 /* Initialize the ALSA interface. */
 static gboolean asound_initialize(VolumeALSAPlugin * vol)
 {
@@ -151,11 +183,12 @@ static gboolean asound_initialize(VolumeALSAPlugin * vol)
     snd_mixer_selem_register(vol->mixer, NULL, NULL);
     snd_mixer_load(vol->mixer);
 
-    /* Find Master element, or Front element, or PCM element, or LineOut element. */
+    /* Find Master element, or Front element, or PCM element, or LineOut element.
+     * If one of these succeeds, master_element is valid. */
     if ( ! asound_find_element(vol, "Master"))
         if ( ! asound_find_element(vol, "Front"))
             if ( ! asound_find_element(vol, "PCM"))
-               if ( ! asound_find_element(vol, "LineOut"))
+                if ( ! asound_find_element(vol, "LineOut"))
                     return FALSE;
 
     /* Set the playback volume range as we wish it. */
@@ -165,22 +198,47 @@ static gboolean asound_initialize(VolumeALSAPlugin * vol)
     int n_fds = snd_mixer_poll_descriptors_count(vol->mixer);
     struct pollfd * fds = g_new0(struct pollfd, n_fds);
 
+    vol->channels = g_new0(GIOChannel *, n_fds);
+    vol->num_channels = n_fds;
+
     snd_mixer_poll_descriptors(vol->mixer, fds, n_fds);
     int i;
     for (i = 0; i < n_fds; ++i)
     {
         GIOChannel* channel = g_io_channel_unix_new(fds[i].fd);
         g_io_add_watch(channel, G_IO_IN | G_IO_HUP, asound_mixer_event, vol);
-        g_io_channel_unref(channel);
+        vol->channels[i] = channel;
     }
     g_free(fds);
     return TRUE;
 }
 
+static void asound_deinitialize(VolumeALSAPlugin * vol)
+{
+    guint i;
+
+    if (vol->mixer_evt_idle != 0) {
+        g_source_remove(vol->mixer_evt_idle);
+        vol->mixer_evt_idle = 0;
+    }
+
+    for (i = 0; i < vol->num_channels; i++) {
+        g_io_channel_shutdown(vol->channels[i], FALSE, NULL);
+        g_io_channel_unref(vol->channels[i]);
+    }
+    g_free(vol->channels);
+    vol->channels = NULL;
+    vol->num_channels = 0;
+
+    snd_mixer_close(vol->mixer);
+    vol->master_element = NULL;
+    /* FIXME: unalloc vol->sid */
+}
+
 /* Get the presence of the mute control from the sound system. */
 static gboolean asound_has_mute(VolumeALSAPlugin * vol)
 {
-    return snd_mixer_selem_has_playback_switch(vol->master_element);
+    return ((vol->master_element != NULL) ? snd_mixer_selem_has_playback_switch(vol->master_element) : FALSE);
 }
 
 /* Get the condition of the mute control from the sound system. */
@@ -189,7 +247,8 @@ static gboolean asound_is_muted(VolumeALSAPlugin * vol)
     /* The switch is on if sound is not muted, and off if the sound is muted.
      * Initialize so that the sound appears unmuted if the control does not exist. */
     int value = 1;
-    snd_mixer_selem_get_playback_switch(vol->master_element, 0, &value);
+    if (vol->master_element != NULL)
+        snd_mixer_selem_get_playback_switch(vol->master_element, 0, &value);
     return (value == 0);
 }
 
@@ -197,10 +256,13 @@ static gboolean asound_is_muted(VolumeALSAPlugin * vol)
  * This implementation returns the average of the Front Left and Front Right channels. */
 static int asound_get_volume(VolumeALSAPlugin * vol)
 {
-    long aleft;
-    long aright;
-    snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
-    snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
+    long aleft = 0;
+    long aright = 0;
+    if (vol->master_element != NULL)
+    {
+        snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
+        snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
+    }
     return (aleft + aright) >> 1;
 }
 
@@ -208,18 +270,72 @@ static int asound_get_volume(VolumeALSAPlugin * vol)
  * This implementation sets the Front Left and Front Right channels to the specified value. */
 static void asound_set_volume(VolumeALSAPlugin * vol, int volume)
 {
-    snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
-    snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
+    if (vol->master_element != NULL)
+    {
+        snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
+        snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
+    }
 }
 
 /*** Graphics ***/
 
+static void volumealsa_update_current_icon(VolumeALSAPlugin * vol)
+{
+    /* Mute status. */
+    gboolean mute = asound_is_muted(vol);
+    int level = asound_get_volume(vol);
+
+    /* Change icon according to mute / volume */
+    const char* icon="audio-volume-muted";
+    const char* icon_panel="audio-volume-muted-panel";
+    const char* icon_fallback=ICONS_MUTE;
+    if (mute)
+    {
+         icon_panel = "audio-volume-muted-panel";
+         icon="audio-volume-muted";
+         icon_fallback=ICONS_MUTE;
+    }
+    else if (level >= 75)
+    {
+         icon_panel = "audio-volume-high-panel";
+         icon="audio-volume-high";
+         icon_fallback=ICONS_VOLUME_HIGH;
+    }
+    else if (level >= 50)
+    {
+         icon_panel = "audio-volume-medium-panel";
+         icon="audio-volume-medium";
+         icon_fallback=ICONS_VOLUME_MEDIUM;
+    }
+    else if (level > 0)
+    {
+         icon_panel = "audio-volume-low-panel";
+         icon="audio-volume-low";
+         icon_fallback=ICONS_VOLUME_LOW;
+    }
+
+    vol->icon_panel = icon_panel;
+    vol->icon = icon;
+    vol->icon_fallback= icon_fallback;
+}
+
 /* Do a full redraw of the display. */
 static void volumealsa_update_display(VolumeALSAPlugin * vol)
 {
     /* Mute status. */
     gboolean mute = asound_is_muted(vol);
-    panel_image_set_from_file(vol->plugin->panel, vol->tray_icon, ((mute) ? ICONS_MUTE : ICONS_VOLUME));
+    int level = asound_get_volume(vol);
+
+    volumealsa_update_current_icon(vol);
+
+    /* Change icon, fallback to default icon if theme doesn't exsit */
+    if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon_panel))
+    {
+        if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon))
+        {
+            lxpanel_image_set_from_file(vol->panel, vol->tray_icon, vol->icon_fallback);
+        }
+    }
 
     g_signal_handler_block(vol->mute_check, vol->mute_check_handler);
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), mute);
@@ -227,7 +343,6 @@ static void volumealsa_update_display(VolumeALSAPlugin * vol)
     g_signal_handler_unblock(vol->mute_check, vol->mute_check_handler);
 
     /* Volume. */
-    int level = asound_get_volume(vol);
     if (vol->volume_scale != NULL)
     {
         g_signal_handler_block(vol->volume_scale, vol->volume_scale_handler);
@@ -237,16 +352,15 @@ static void volumealsa_update_display(VolumeALSAPlugin * vol)
 
     /* Display current level in tooltip. */
     char * tooltip = g_strdup_printf("%s %d", _("Volume control"), level);
-    gtk_widget_set_tooltip_text(vol->plugin->pwid, tooltip);
+    gtk_widget_set_tooltip_text(vol->plugin, tooltip);
     g_free(tooltip);
 }
 
+
 /* Handler for "button-press-event" signal on main widget. */
-static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, VolumeALSAPlugin * vol)
+static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * panel)
 {
-    /* Standard right-click handling. */
-    if (plugin_button_press_event(widget, event, vol->plugin))
-        return TRUE;
+    VolumeALSAPlugin * vol = lxpanel_plugin_get_data(widget);
 
     /* Left-click.  Show or hide the popup window. */
     if (event->button == 1)
@@ -258,7 +372,6 @@ static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton
         }
         else
         {
-            gtk_window_set_position(GTK_WINDOW(vol->popup_window), GTK_WIN_POS_MOUSE);
             gtk_widget_show_all(vol->popup_window);
             vol->show_popup = TRUE;
         }
@@ -281,6 +394,23 @@ static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event,
     return FALSE;
 }
 
+/* Handler for "map" signal on popup window. */
+static void volumealsa_popup_map(GtkWidget * widget, VolumeALSAPlugin * vol)
+{
+    lxpanel_plugin_adjust_popup_position(widget, vol->plugin);
+}
+
+static void volumealsa_theme_change(GtkWidget * widget, VolumeALSAPlugin * vol)
+{
+    if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon_panel))
+    {
+        if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon))
+        {
+            lxpanel_image_set_from_file(vol->panel, vol->tray_icon, vol->icon_fallback);
+        }
+    }
+}
+
 /* Handler for "value_changed" signal on popup window vertical scale. */
 static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol)
 {
@@ -314,37 +444,41 @@ static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin *
     gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
 
     /* Reflect the mute toggle to the sound system. */
-    int chn;
-    for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
-        snd_mixer_selem_set_playback_switch(vol->master_element, chn, ((active) ? 0 : 1));
+    if (vol->master_element != NULL)
+    {
+        int chn;
+        for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
+            snd_mixer_selem_set_playback_switch(vol->master_element, chn, ((active) ? 0 : 1));
+    }
 
     /* Redraw the controls. */
     volumealsa_update_display(vol);
 }
 
 /* Build the window that appears when the top level widget is clicked. */
-static void volumealsa_build_popup_window(Plugin * p)
+static void volumealsa_build_popup_window(GtkWidget *p)
 {
-    VolumeALSAPlugin * vol = p->priv;
+    VolumeALSAPlugin * vol = lxpanel_plugin_get_data(p);
 
     /* Create a new window. */
-    vol->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    vol->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
     gtk_window_set_decorated(GTK_WINDOW(vol->popup_window), FALSE);
     gtk_container_set_border_width(GTK_CONTAINER(vol->popup_window), 5);
     gtk_window_set_default_size(GTK_WINDOW(vol->popup_window), 80, 140);
     gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->popup_window), TRUE);
     gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->popup_window), TRUE);
-    gtk_window_set_type_hint(GTK_WINDOW(vol->popup_window), GDK_WINDOW_TYPE_HINT_DIALOG);
+    gtk_window_set_type_hint(GTK_WINDOW(vol->popup_window), GDK_WINDOW_TYPE_HINT_UTILITY);
 
-    /* Focus-out signal. */
-    g_signal_connect(G_OBJECT(vol->popup_window), "focus_out_event", G_CALLBACK(volumealsa_popup_focus_out), vol);
+    /* Connect signals. */
+    g_signal_connect(G_OBJECT(vol->popup_window), "focus-out-event", G_CALLBACK(volumealsa_popup_focus_out), vol);
+    g_signal_connect(G_OBJECT(vol->popup_window), "map", G_CALLBACK(volumealsa_popup_map), vol);
 
     /* Create a scrolled window as the child of the top level window. */
     GtkWidget * scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
     gtk_container_set_border_width (GTK_CONTAINER(scrolledwindow), 0);
     gtk_widget_show(scrolledwindow);
     gtk_container_add(GTK_CONTAINER(vol->popup_window), scrolledwindow);
-    GTK_WIDGET_UNSET_FLAGS(scrolledwindow, GTK_CAN_FOCUS);
+    gtk_widget_set_can_focus(scrolledwindow, FALSE);
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_NONE);
 
@@ -370,7 +504,7 @@ static void volumealsa_build_popup_window(Plugin * p)
     gtk_box_pack_start(GTK_BOX(box), vol->volume_scale, TRUE, TRUE, 0);
 
     /* Value-changed and scroll-event signals. */
-    vol->volume_scale_handler = g_signal_connect(vol->volume_scale, "value_changed", G_CALLBACK(volumealsa_popup_scale_changed), vol);
+    vol->volume_scale_handler = g_signal_connect(vol->volume_scale, "value-changed", G_CALLBACK(volumealsa_popup_scale_changed), vol);
     g_signal_connect(vol->volume_scale, "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol);
 
     /* Create a check button as the child of the vertical box. */
@@ -379,81 +513,139 @@ static void volumealsa_build_popup_window(Plugin * p)
     vol->mute_check_handler = g_signal_connect(vol->mute_check, "toggled", G_CALLBACK(volumealsa_popup_mute_toggled), vol);
 
     /* Set background to default. */
-    gtk_widget_set_style(viewport, p->panel->defstyle);
+    gtk_widget_set_style(viewport, panel_get_defstyle(vol->panel));
 }
 
 /* Plugin constructor. */
-static int volumealsa_constructor(Plugin * p, char ** fp)
+static GtkWidget *volumealsa_constructor(LXPanel *panel, config_setting_t *settings)
 {
     /* Allocate and initialize plugin context and set into Plugin private data pointer. */
     VolumeALSAPlugin * vol = g_new0(VolumeALSAPlugin, 1);
-    vol->plugin = p;
-    p->priv = vol;
+    GtkWidget *p;
 
     /* Initialize ALSA.  If that fails, present nothing. */
     if ( ! asound_initialize(vol))
-        return 1;
+    {
+        volumealsa_destructor(vol);
+        return NULL;
+    }
 
     /* Allocate top level widget and set into Plugin widget pointer. */
-    p->pwid = gtk_event_box_new();
-    gtk_widget_add_events(p->pwid, GDK_BUTTON_PRESS_MASK);
-    gtk_widget_set_tooltip_text(p->pwid, _("Volume control"));
+    vol->panel = panel;
+    vol->plugin = p = gtk_event_box_new();
+    lxpanel_plugin_set_data(p, vol, volumealsa_destructor);
+    gtk_widget_add_events(p, GDK_BUTTON_PRESS_MASK);
+    gtk_widget_set_tooltip_text(p, _("Volume control"));
 
     /* Allocate icon as a child of top level. */
     vol->tray_icon = gtk_image_new();
-    gtk_container_add(GTK_CONTAINER(p->pwid), vol->tray_icon);
+    gtk_container_add(GTK_CONTAINER(p), vol->tray_icon);
 
     /* Initialize window to appear when icon clicked. */
     volumealsa_build_popup_window(p);
 
     /* Connect signals. */
-    g_signal_connect(G_OBJECT(p->pwid), "button-press-event", G_CALLBACK(volumealsa_button_press_event), vol);
-    g_signal_connect(G_OBJECT(p->pwid), "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol );
+    g_signal_connect(G_OBJECT(p), "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol );
+    g_signal_connect(panel_get_icon_theme(panel), "changed", G_CALLBACK(volumealsa_theme_change), vol );
 
     /* Update the display, show the widget, and return. */
     volumealsa_update_display(vol);
-    gtk_widget_show_all(p->pwid);
-    return 1;
+    gtk_widget_show_all(p);
+    return p;
 }
 
 /* Plugin destructor. */
-static void volumealsa_destructor(Plugin * p)
+static void volumealsa_destructor(gpointer user_data)
 {
-    VolumeALSAPlugin * vol = (VolumeALSAPlugin *) p->priv;
+    VolumeALSAPlugin * vol = (VolumeALSAPlugin *) user_data;
 
-    /* Remove the periodic timer. */
-    if (vol->mixer_evt_idle != 0)
-        g_source_remove(vol->mixer_evt_idle);
+    asound_deinitialize(vol);
 
     /* If the dialog box is open, dismiss it. */
     if (vol->popup_window != NULL)
         gtk_widget_destroy(vol->popup_window);
 
+    if (vol->restart_idle)
+        g_source_remove(vol->restart_idle);
+
     /* Deallocate all memory. */
     g_free(vol);
 }
 
-/* Callback when panel configuration changes. */
-static void volumealsa_panel_configuration_changed(Plugin * p)
+/* Callback when the configuration dialog is to be shown. */
+
+static GtkWidget *volumealsa_configure(LXPanel *panel, GtkWidget *p)
 {
-    /* Do a full redraw. */
-    volumealsa_update_display((VolumeALSAPlugin *) p->priv);
-}
+    const gchar *command_line = NULL;
 
-/* Plugin descriptor. */
-PluginClass volumealsa_plugin_class = {
+    if (g_find_program_in_path("pulseaudio"))
+    {
+     /* Assume that when pulseaudio is installed, it's launching every time */
+        if (g_find_program_in_path("gnome-sound-applet"))
+        {
+            command_line = "gnome-sound-applet";
+        }
+        else
+        {
+            if (g_find_program_in_path("pavucontrol"))
+            {
+                command_line = "pavucontrol";
+            }
+        }
+    }
+
+    /* Fallback to alsamixer when PA is not running, or when no PA utility is find */
+    if (command_line == NULL)
+    {
+        if (g_find_program_in_path("gnome-alsamixer"))
+        {
+            command_line = "gnome-alsamixer";
+        }
+        else
+        {
+            if (g_find_program_in_path("alsamixer"))
+            {
+                if (g_find_program_in_path("xterm"))
+                {
+                    command_line = "xterm -e alsamixer";
+                }
+            }
+        }
+    }
 
-    PLUGINCLASS_VERSIONING,
+    if (command_line)
+    {
+        fm_launch_command_simple(NULL, NULL, G_APP_INFO_CREATE_NONE,
+                                 command_line, NULL);
+    }
+    else
+    {
+        fm_show_error(NULL,
+                      _("Error, you need to install an application to configure the sound (pavucontrol, alsamixer ...)"),
+                      NULL);
+    }
 
-    type : "volumealsa",
-    name : N_("Volume Control"),
-    version: "1.0",
-    description : "Display and control volume for ALSA",
+    return NULL;
+}
 
-    constructor : volumealsa_constructor,
-    destructor  : volumealsa_destructor,
-    config : NULL,
-    save : NULL,
-    panel_configuration_changed : volumealsa_panel_configuration_changed
+/* Callback when panel configuration changes. */
+static void volumealsa_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
+{
+    /* Do a full redraw. */
+    volumealsa_update_display(lxpanel_plugin_get_data(p));
+}
 
+FM_DEFINE_MODULE(lxpanel_gtk, volumealsa)
+
+/* Plugin descriptor. */
+LXPanelPluginInit fm_module_init_lxpanel_gtk = {
+    .name = N_("Volume Control"),
+    .description = N_("Display and control volume for ALSA"),
+
+    .new_instance = volumealsa_constructor,
+    .config = volumealsa_configure,
+    .reconfigure = volumealsa_panel_configuration_changed,
+    .button_press_event = volumealsa_button_press_event
 };
+
+/* vim: set sw=4 et sts=4 : */