Adding upstream version 0.5.8.
[debian/lxpanel.git] / src / plugins / volumealsa / volumealsa.c
CommitLineData
6cc5e1a6
DB
1/**
2 * Copyright (c) 2008 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include <gtk/gtk.h>
20#include <stdlib.h>
21#include <fcntl.h>
22#include <unistd.h>
23#include <glib.h>
24#include <glib/gi18n.h>
25#include <gdk-pixbuf/gdk-pixbuf.h>
26#include <alsa/asoundlib.h>
96dc8a45 27#include <poll.h>
6cc5e1a6
DB
28#include "panel.h"
29#include "misc.h"
30#include "plugin.h"
31#include "dbg.h"
32
33#define ICONS_VOLUME PACKAGE_DATA_DIR "/lxpanel/images/volume.png"
34#define ICONS_MUTE PACKAGE_DATA_DIR "/lxpanel/images/mute.png"
35
36typedef struct {
2ba86315
DB
37
38 /* Graphics. */
39 Plugin * plugin; /* Back pointer to plugin */
40 GtkWidget * tray_icon; /* Displayed image */
41 GtkWidget * popup_window; /* Top level window for popup */
42 GtkWidget * volume_scale; /* Scale for volume */
43 GtkWidget * mute_check; /* Checkbox for mute state */
44 gboolean show_popup; /* Toggle to show and hide the popup on left click */
45 guint volume_scale_handler; /* Handler for vscale widget */
46 guint mute_check_handler; /* Handler for mute_check widget */
47
48 /* ALSA interface. */
49 snd_mixer_t * mixer; /* The mixer */
50 snd_mixer_selem_id_t * sid; /* The element ID */
51 snd_mixer_elem_t * master_element; /* The Master element */
52 guint mixer_evt_idle; /* Timer to handle restarting poll */
53} VolumeALSAPlugin;
54
55static gboolean asound_find_element(VolumeALSAPlugin * vol, const char * ename);
56static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol);
57static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer);
58static gboolean asound_initialize(VolumeALSAPlugin * vol);
59static gboolean asound_has_mute(VolumeALSAPlugin * vol);
60static gboolean asound_is_muted(VolumeALSAPlugin * vol);
61static int asound_get_volume(VolumeALSAPlugin * vol);
62static void asound_set_volume(VolumeALSAPlugin * vol, int volume);
63static void volumealsa_update_display(VolumeALSAPlugin * vol);
64static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, VolumeALSAPlugin * vol);
65static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event, VolumeALSAPlugin * vol);
32a67dc7 66static void volumealsa_popup_map(GtkWidget * widget, VolumeALSAPlugin * vol);
2ba86315
DB
67static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol);
68static void volumealsa_popup_scale_scrolled(GtkScale * scale, GdkEventScroll * evt, VolumeALSAPlugin * vol);
69static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin * vol);
70static void volumealsa_build_popup_window(Plugin * p);
71static int volumealsa_constructor(Plugin * p, char ** fp);
72static void volumealsa_destructor(Plugin * p);
73static void volumealsa_panel_configuration_changed(Plugin * p);
74
75/*** ALSA ***/
76
77static gboolean asound_find_element(VolumeALSAPlugin * vol, const char * ename)
6cc5e1a6 78{
2ba86315
DB
79 for (
80 vol->master_element = snd_mixer_first_elem(vol->mixer);
81 vol->master_element != NULL;
82 vol->master_element = snd_mixer_elem_next(vol->master_element))
83 {
6cc5e1a6 84 snd_mixer_selem_get_id(vol->master_element, vol->sid);
2ba86315
DB
85 if ((snd_mixer_selem_is_active(vol->master_element))
86 && (strcmp(ename, snd_mixer_selem_id_get_name(vol->sid)) == 0))
6cc5e1a6 87 return TRUE;
6cc5e1a6 88 }
6cc5e1a6
DB
89 return FALSE;
90}
91
96dc8a45
DB
92/* NOTE by PCMan:
93 * This is magic! Since ALSA uses its own machanism to handle this part.
94 * After polling of mixer fds, it requires that we should call
95 * snd_mixer_handle_events to clear all pending mixer events.
96 * However, when using the glib IO channels approach, we don't have
97 * poll() and snd_mixer_poll_descriptors_revents(). Due to the design of
98 * glib, on_mixer_event() will be called for every fd whose status was
99 * changed. So, after each poll(), it's called for several times,
100 * not just once. Therefore, we cannot call snd_mixer_handle_events()
101 * directly in the event handler. Otherwise, it will get called for
102 * several times, which might clear unprocessed pending events in the queue.
103 * So, here we call it once in the event callback for the first fd.
104 * Then, we don't call it for the following fds. After all fds with changed
105 * status are handled, we remove this restriction in an idle handler.
106 * The next time the event callback is involked for the first fs, we can
107 * call snd_mixer_handle_events() again. Racing shouldn't happen here
108 * because the idle handler has the same priority as the io channel callback.
109 * So, io callbacks for future pending events should be in the next gmain
110 * iteration, and won't be affected.
111 */
2ba86315
DB
112
113static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol)
96dc8a45
DB
114{
115 vol->mixer_evt_idle = 0;
116 return FALSE;
117}
118
2ba86315
DB
119/* Handler for I/O event on ALSA channel. */
120static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer)
96dc8a45 121{
2ba86315
DB
122 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) vol_gpointer;
123
124 if (vol->mixer_evt_idle == 0)
96dc8a45 125 {
2ba86315
DB
126 vol->mixer_evt_idle = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) asound_reset_mixer_evt_idle, vol, NULL);
127 snd_mixer_handle_events(vol->mixer);
96dc8a45
DB
128 }
129
2ba86315 130 if (cond & G_IO_IN)
96dc8a45
DB
131 {
132 /* the status of mixer is changed. update of display is needed. */
2ba86315 133 volumealsa_update_display(vol);
96dc8a45 134 }
96dc8a45 135
2ba86315
DB
136 if (cond & G_IO_HUP)
137 {
138 /* This means there're some problems with alsa. */
96dc8a45
DB
139 return FALSE;
140 }
141
142 return TRUE;
143}
144
2ba86315
DB
145/* Initialize the ALSA interface. */
146static gboolean asound_initialize(VolumeALSAPlugin * vol)
6cc5e1a6 147{
2ba86315 148 /* Access the "default" device. */
6cc5e1a6
DB
149 snd_mixer_selem_id_alloca(&vol->sid);
150 snd_mixer_open(&vol->mixer, 0);
151 snd_mixer_attach(vol->mixer, "default");
152 snd_mixer_selem_register(vol->mixer, NULL, NULL);
153 snd_mixer_load(vol->mixer);
154
4652f59b
DB
155 /* Find Master element, or Front element, or PCM element, or LineOut element.
156 * If one of these succeeds, master_element is valid. */
2ba86315
DB
157 if ( ! asound_find_element(vol, "Master"))
158 if ( ! asound_find_element(vol, "Front"))
159 if ( ! asound_find_element(vol, "PCM"))
160 if ( ! asound_find_element(vol, "LineOut"))
39c13576 161 return FALSE;
6cc5e1a6 162
2ba86315 163 /* Set the playback volume range as we wish it. */
6cc5e1a6
DB
164 snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);
165
2ba86315
DB
166 /* Listen to events from ALSA. */
167 int n_fds = snd_mixer_poll_descriptors_count(vol->mixer);
168 struct pollfd * fds = g_new0(struct pollfd, n_fds);
96dc8a45 169
2ba86315
DB
170 snd_mixer_poll_descriptors(vol->mixer, fds, n_fds);
171 int i;
172 for (i = 0; i < n_fds; ++i)
96dc8a45 173 {
2ba86315
DB
174 GIOChannel* channel = g_io_channel_unix_new(fds[i].fd);
175 g_io_add_watch(channel, G_IO_IN | G_IO_HUP, asound_mixer_event, vol);
176 g_io_channel_unref(channel);
96dc8a45 177 }
2ba86315 178 g_free(fds);
6cc5e1a6
DB
179 return TRUE;
180}
181
2ba86315
DB
182/* Get the presence of the mute control from the sound system. */
183static gboolean asound_has_mute(VolumeALSAPlugin * vol)
184{
4652f59b 185 return ((vol->master_element != NULL) ? snd_mixer_selem_has_playback_switch(vol->master_element) : FALSE);
2ba86315
DB
186}
187
188/* Get the condition of the mute control from the sound system. */
189static gboolean asound_is_muted(VolumeALSAPlugin * vol)
6cc5e1a6 190{
2ba86315
DB
191 /* The switch is on if sound is not muted, and off if the sound is muted.
192 * Initialize so that the sound appears unmuted if the control does not exist. */
193 int value = 1;
4652f59b
DB
194 if (vol->master_element != NULL)
195 snd_mixer_selem_get_playback_switch(vol->master_element, 0, &value);
2ba86315
DB
196 return (value == 0);
197}
198
199/* Get the volume from the sound system.
200 * This implementation returns the average of the Front Left and Front Right channels. */
201static int asound_get_volume(VolumeALSAPlugin * vol)
202{
4652f59b
DB
203 long aleft = 0;
204 long aright = 0;
205 if (vol->master_element != NULL)
206 {
207 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
208 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
209 }
6cc5e1a6
DB
210 return (aleft + aright) >> 1;
211}
212
2ba86315
DB
213/* Set the volume to the sound system.
214 * This implementation sets the Front Left and Front Right channels to the specified value. */
215static void asound_set_volume(VolumeALSAPlugin * vol, int volume)
6cc5e1a6 216{
4652f59b
DB
217 if (vol->master_element != NULL)
218 {
219 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
220 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
221 }
6cc5e1a6
DB
222}
223
2ba86315
DB
224/*** Graphics ***/
225
226/* Do a full redraw of the display. */
227static void volumealsa_update_display(VolumeALSAPlugin * vol)
6cc5e1a6 228{
2ba86315
DB
229 /* Mute status. */
230 gboolean mute = asound_is_muted(vol);
32a67dc7
DB
231
232 if ( ! panel_image_set_icon_theme(vol->plugin->panel, vol->tray_icon, ((mute) ? "audio-volume-muted" : "audio-volume-high")))
233 {
234 panel_image_set_from_file(vol->plugin->panel, vol->tray_icon, ((mute) ? ICONS_MUTE : ICONS_VOLUME));
235 }
2ba86315
DB
236
237 g_signal_handler_block(vol->mute_check, vol->mute_check_handler);
238 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), mute);
239 gtk_widget_set_sensitive(vol->mute_check, asound_has_mute(vol));
240 g_signal_handler_unblock(vol->mute_check, vol->mute_check_handler);
241
242 /* Volume. */
243 int level = asound_get_volume(vol);
244 if (vol->volume_scale != NULL)
245 {
246 g_signal_handler_block(vol->volume_scale, vol->volume_scale_handler);
247 gtk_range_set_value(GTK_RANGE(vol->volume_scale), asound_get_volume(vol));
248 g_signal_handler_unblock(vol->volume_scale, vol->volume_scale_handler);
249 }
250
251 /* Display current level in tooltip. */
252 char * tooltip = g_strdup_printf("%s %d", _("Volume control"), level);
253 gtk_widget_set_tooltip_text(vol->plugin->pwid, tooltip);
254 g_free(tooltip);
6cc5e1a6
DB
255}
256
2ba86315
DB
257/* Handler for "button-press-event" signal on main widget. */
258static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, VolumeALSAPlugin * vol)
6cc5e1a6 259{
2ba86315
DB
260 /* Standard right-click handling. */
261 if (plugin_button_press_event(widget, event, vol->plugin))
6cc5e1a6 262 return TRUE;
2ba86315
DB
263
264 /* Left-click. Show or hide the popup window. */
265 if (event->button == 1)
266 {
267 if (vol->show_popup)
268 {
269 gtk_widget_hide(vol->popup_window);
270 vol->show_popup = FALSE;
271 }
272 else
273 {
2ba86315
DB
274 gtk_widget_show_all(vol->popup_window);
275 vol->show_popup = TRUE;
276 }
6cc5e1a6
DB
277 }
278
2ba86315
DB
279 /* Middle-click. Toggle the mute status. */
280 else if (event->button == 2)
281 {
282 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), ! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check)));
6cc5e1a6
DB
283 }
284 return TRUE;
285}
286
2ba86315
DB
287/* Handler for "focus-out" signal on popup window. */
288static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event, VolumeALSAPlugin * vol)
6cc5e1a6 289{
2ba86315
DB
290 /* Hide the widget. */
291 gtk_widget_hide(vol->popup_window);
292 vol->show_popup = FALSE;
293 return FALSE;
6cc5e1a6
DB
294}
295
32a67dc7
DB
296/* Handler for "map" signal on popup window. */
297static void volumealsa_popup_map(GtkWidget * widget, VolumeALSAPlugin * vol)
298{
299 plugin_adjust_popup_position(widget, vol->plugin);
300}
301
2ba86315
DB
302/* Handler for "value_changed" signal on popup window vertical scale. */
303static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol)
96dc8a45 304{
2ba86315
DB
305 /* Reflect the value of the control to the sound system. */
306 asound_set_volume(vol, gtk_range_get_value(range));
307
308 /* Redraw the controls. */
309 volumealsa_update_display(vol);
310}
311
312/* Handler for "scroll-event" signal on popup window vertical scale. */
313static void volumealsa_popup_scale_scrolled(GtkScale * scale, GdkEventScroll * evt, VolumeALSAPlugin * vol)
314{
315 /* Get the state of the vertical scale. */
316 gdouble val = gtk_range_get_value(GTK_RANGE(vol->volume_scale));
317
318 /* Dispatch on scroll direction to update the value. */
319 if ((evt->direction == GDK_SCROLL_UP) || (evt->direction == GDK_SCROLL_LEFT))
96dc8a45 320 val += 2;
2ba86315 321 else
96dc8a45 322 val -= 2;
2ba86315
DB
323
324 /* Reset the state of the vertical scale. This provokes a "value_changed" event. */
325 gtk_range_set_value(GTK_RANGE(vol->volume_scale), CLAMP((int)val, 0, 100));
96dc8a45
DB
326}
327
2ba86315
DB
328/* Handler for "toggled" signal on popup window mute checkbox. */
329static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin * vol)
6cc5e1a6 330{
2ba86315
DB
331 /* Get the state of the mute toggle. */
332 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
333
334 /* Reflect the mute toggle to the sound system. */
4652f59b
DB
335 if (vol->master_element != NULL)
336 {
337 int chn;
338 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
339 snd_mixer_selem_set_playback_switch(vol->master_element, chn, ((active) ? 0 : 1));
340 }
6cc5e1a6 341
2ba86315
DB
342 /* Redraw the controls. */
343 volumealsa_update_display(vol);
6cc5e1a6
DB
344}
345
2ba86315
DB
346/* Build the window that appears when the top level widget is clicked. */
347static void volumealsa_build_popup_window(Plugin * p)
6cc5e1a6 348{
2ba86315
DB
349 VolumeALSAPlugin * vol = p->priv;
350
351 /* Create a new window. */
352 vol->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
353 gtk_window_set_decorated(GTK_WINDOW(vol->popup_window), FALSE);
354 gtk_container_set_border_width(GTK_CONTAINER(vol->popup_window), 5);
355 gtk_window_set_default_size(GTK_WINDOW(vol->popup_window), 80, 140);
356 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->popup_window), TRUE);
357 gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->popup_window), TRUE);
358 gtk_window_set_type_hint(GTK_WINDOW(vol->popup_window), GDK_WINDOW_TYPE_HINT_DIALOG);
359
32a67dc7 360 /* Connect signals. */
2ba86315 361 g_signal_connect(G_OBJECT(vol->popup_window), "focus_out_event", G_CALLBACK(volumealsa_popup_focus_out), vol);
32a67dc7 362 g_signal_connect(G_OBJECT(vol->popup_window), "map", G_CALLBACK(volumealsa_popup_map), vol);
2ba86315
DB
363
364 /* Create a scrolled window as the child of the top level window. */
365 GtkWidget * scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
366 gtk_container_set_border_width (GTK_CONTAINER(scrolledwindow), 0);
367 gtk_widget_show(scrolledwindow);
368 gtk_container_add(GTK_CONTAINER(vol->popup_window), scrolledwindow);
369 GTK_WIDGET_UNSET_FLAGS(scrolledwindow, GTK_CAN_FOCUS);
6cc5e1a6
DB
370 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
371 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_NONE);
372
2ba86315
DB
373 /* Create a viewport as the child of the scrolled window. */
374 GtkWidget * viewport = gtk_viewport_new(NULL, NULL);
375 gtk_container_add(GTK_CONTAINER(scrolledwindow), viewport);
376 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
6cc5e1a6
DB
377 gtk_widget_show(viewport);
378
2ba86315
DB
379 /* Create a frame as the child of the viewport. */
380 GtkWidget * frame = gtk_frame_new(_("Volume"));
6cc5e1a6
DB
381 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
382 gtk_container_add(GTK_CONTAINER(viewport), frame);
383
2ba86315
DB
384 /* Create a vertical box as the child of the frame. */
385 GtkWidget * box = gtk_vbox_new(FALSE, 0);
386 gtk_container_add(GTK_CONTAINER(frame), box);
6cc5e1a6 387
2ba86315
DB
388 /* Create a vertical scale as the child of the vertical box. */
389 vol->volume_scale = gtk_vscale_new(GTK_ADJUSTMENT(gtk_adjustment_new(100, 0, 100, 0, 0, 0)));
390 gtk_scale_set_draw_value(GTK_SCALE(vol->volume_scale), FALSE);
391 gtk_range_set_inverted(GTK_RANGE(vol->volume_scale), TRUE);
392 gtk_box_pack_start(GTK_BOX(box), vol->volume_scale, TRUE, TRUE, 0);
6cc5e1a6 393
2ba86315
DB
394 /* Value-changed and scroll-event signals. */
395 vol->volume_scale_handler = g_signal_connect(vol->volume_scale, "value_changed", G_CALLBACK(volumealsa_popup_scale_changed), vol);
396 g_signal_connect(vol->volume_scale, "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol);
6cc5e1a6 397
2ba86315 398 /* Create a check button as the child of the vertical box. */
96dc8a45 399 vol->mute_check = gtk_check_button_new_with_label(_("Mute"));
96dc8a45 400 gtk_box_pack_end(GTK_BOX(box), vol->mute_check, FALSE, FALSE, 0);
2ba86315 401 vol->mute_check_handler = g_signal_connect(vol->mute_check, "toggled", G_CALLBACK(volumealsa_popup_mute_toggled), vol);
6cc5e1a6 402
2ba86315 403 /* Set background to default. */
6cc5e1a6
DB
404 gtk_widget_set_style(viewport, p->panel->defstyle);
405}
406
2ba86315
DB
407/* Plugin constructor. */
408static int volumealsa_constructor(Plugin * p, char ** fp)
6cc5e1a6 409{
2ba86315
DB
410 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
411 VolumeALSAPlugin * vol = g_new0(VolumeALSAPlugin, 1);
6cc5e1a6 412 vol->plugin = p;
6cc5e1a6
DB
413 p->priv = vol;
414
2ba86315
DB
415 /* Initialize ALSA. If that fails, present nothing. */
416 if ( ! asound_initialize(vol))
417 return 1;
6cc5e1a6 418
2ba86315
DB
419 /* Allocate top level widget and set into Plugin widget pointer. */
420 p->pwid = gtk_event_box_new();
421 gtk_widget_add_events(p->pwid, GDK_BUTTON_PRESS_MASK);
422 gtk_widget_set_tooltip_text(p->pwid, _("Volume control"));
6cc5e1a6 423
2ba86315
DB
424 /* Allocate icon as a child of top level. */
425 vol->tray_icon = gtk_image_new();
426 gtk_container_add(GTK_CONTAINER(p->pwid), vol->tray_icon);
6cc5e1a6 427
2ba86315
DB
428 /* Initialize window to appear when icon clicked. */
429 volumealsa_build_popup_window(p);
6cc5e1a6 430
2ba86315
DB
431 /* Connect signals. */
432 g_signal_connect(G_OBJECT(p->pwid), "button-press-event", G_CALLBACK(volumealsa_button_press_event), vol);
433 g_signal_connect(G_OBJECT(p->pwid), "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol );
6cc5e1a6 434
2ba86315
DB
435 /* Update the display, show the widget, and return. */
436 volumealsa_update_display(vol);
437 gtk_widget_show_all(p->pwid);
438 return 1;
439}
6cc5e1a6 440
2ba86315
DB
441/* Plugin destructor. */
442static void volumealsa_destructor(Plugin * p)
443{
444 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) p->priv;
6cc5e1a6 445
2ba86315
DB
446 /* Remove the periodic timer. */
447 if (vol->mixer_evt_idle != 0)
448 g_source_remove(vol->mixer_evt_idle);
6cc5e1a6 449
2ba86315
DB
450 /* If the dialog box is open, dismiss it. */
451 if (vol->popup_window != NULL)
452 gtk_widget_destroy(vol->popup_window);
6cc5e1a6 453
2ba86315
DB
454 /* Deallocate all memory. */
455 g_free(vol);
6cc5e1a6
DB
456}
457
2ba86315
DB
458/* Callback when panel configuration changes. */
459static void volumealsa_panel_configuration_changed(Plugin * p)
460{
461 /* Do a full redraw. */
462 volumealsa_update_display((VolumeALSAPlugin *) p->priv);
463}
6cc5e1a6 464
2ba86315 465/* Plugin descriptor. */
6cc5e1a6 466PluginClass volumealsa_plugin_class = {
2ba86315
DB
467
468 PLUGINCLASS_VERSIONING,
6cc5e1a6
DB
469
470 type : "volumealsa",
471 name : N_("Volume Control"),
472 version: "1.0",
473 description : "Display and control volume for ALSA",
474
475 constructor : volumealsa_constructor,
476 destructor : volumealsa_destructor,
477 config : NULL,
2ba86315
DB
478 save : NULL,
479 panel_configuration_changed : volumealsa_panel_configuration_changed
480
6cc5e1a6 481};