Fix memory leak in 'volumealsa' plugin.
[lxde/lxpanel.git] / plugins / volumealsa / volumealsa.c
CommitLineData
c044bccf 1/**
b840f7cc 2 * Copyright (c) 2008-2014 LxDE Developers, see the file AUTHORS for details.
49db0a37
FC
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>
55ccbddf 27#include <poll.h>
b285684e
AG
28#include <libfm/fm-gtk.h>
29#include "plugin.h"
49db0a37 30
0b806437
AG
31#define ICONS_VOLUME_HIGH PACKAGE_DATA_DIR "/images/volume-high.png"
32#define ICONS_VOLUME_MEDIUM PACKAGE_DATA_DIR "/images/volume-medium.png"
33#define ICONS_VOLUME_LOW PACKAGE_DATA_DIR "/images/volume-low.png"
34#define ICONS_MUTE PACKAGE_DATA_DIR "/images/mute.png"
49db0a37
FC
35
36typedef struct {
f0861dee 37
38 /* Graphics. */
b285684e 39 GtkWidget * plugin; /* Back pointer to the widget */
a7bd16a4 40 LXPanel * panel; /* Back pointer to panel */
72312227 41 config_setting_t * settings; /* Plugin settings */
f0861dee 42 GtkWidget * tray_icon; /* Displayed image */
43 GtkWidget * popup_window; /* Top level window for popup */
44 GtkWidget * volume_scale; /* Scale for volume */
45 GtkWidget * mute_check; /* Checkbox for mute state */
46 gboolean show_popup; /* Toggle to show and hide the popup on left click */
47 guint volume_scale_handler; /* Handler for vscale widget */
48 guint mute_check_handler; /* Handler for mute_check widget */
49
50 /* ALSA interface. */
51 snd_mixer_t * mixer; /* The mixer */
52 snd_mixer_selem_id_t * sid; /* The element ID */
53 snd_mixer_elem_t * master_element; /* The Master element */
54 guint mixer_evt_idle; /* Timer to handle restarting poll */
b285684e 55 guint restart_idle;
ad987a0c
HG
56
57 /* unloading and error handling */
58 GIOChannel **channels; /* Channels that we listen to */
59 guint num_channels; /* Number of channels */
7192ab49
JL
60
61 /* Icons */
62 const char* icon;
63 const char* icon_panel;
64 const char* icon_fallback;
65
f0861dee 66} VolumeALSAPlugin;
67
ad987a0c 68static gboolean asound_restart(gpointer vol_gpointer);
f0861dee 69static gboolean asound_initialize(VolumeALSAPlugin * vol);
ad987a0c 70static void asound_deinitialize(VolumeALSAPlugin * vol);
f0861dee 71static void volumealsa_update_display(VolumeALSAPlugin * vol);
b285684e 72static void volumealsa_destructor(gpointer user_data);
f0861dee 73
74/*** ALSA ***/
75
76static gboolean asound_find_element(VolumeALSAPlugin * vol, const char * ename)
49db0a37 77{
f0861dee 78 for (
79 vol->master_element = snd_mixer_first_elem(vol->mixer);
80 vol->master_element != NULL;
81 vol->master_element = snd_mixer_elem_next(vol->master_element))
82 {
4542c20d 83 snd_mixer_selem_get_id(vol->master_element, vol->sid);
f0861dee 84 if ((snd_mixer_selem_is_active(vol->master_element))
85 && (strcmp(ename, snd_mixer_selem_id_get_name(vol->sid)) == 0))
4542c20d 86 return TRUE;
4542c20d 87 }
4542c20d 88 return FALSE;
49db0a37
FC
89}
90
55ccbddf
HJYP
91/* NOTE by PCMan:
92 * This is magic! Since ALSA uses its own machanism to handle this part.
93 * After polling of mixer fds, it requires that we should call
94 * snd_mixer_handle_events to clear all pending mixer events.
95 * However, when using the glib IO channels approach, we don't have
96 * poll() and snd_mixer_poll_descriptors_revents(). Due to the design of
97 * glib, on_mixer_event() will be called for every fd whose status was
98 * changed. So, after each poll(), it's called for several times,
99 * not just once. Therefore, we cannot call snd_mixer_handle_events()
100 * directly in the event handler. Otherwise, it will get called for
101 * several times, which might clear unprocessed pending events in the queue.
102 * So, here we call it once in the event callback for the first fd.
103 * Then, we don't call it for the following fds. After all fds with changed
104 * status are handled, we remove this restriction in an idle handler.
105 * The next time the event callback is involked for the first fs, we can
106 * call snd_mixer_handle_events() again. Racing shouldn't happen here
107 * because the idle handler has the same priority as the io channel callback.
108 * So, io callbacks for future pending events should be in the next gmain
109 * iteration, and won't be affected.
110 */
f0861dee 111
112static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol)
55ccbddf 113{
251cfd3e
AG
114 if (!g_source_is_destroyed(g_main_current_source()))
115 vol->mixer_evt_idle = 0;
55ccbddf
HJYP
116 return FALSE;
117}
118
f0861dee 119/* Handler for I/O event on ALSA channel. */
120static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer)
55ccbddf 121{
f0861dee 122 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) vol_gpointer;
e0eb2f82 123 int res = 0;
f0861dee 124
125 if (vol->mixer_evt_idle == 0)
0fdd5153 126 {
f0861dee 127 vol->mixer_evt_idle = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) asound_reset_mixer_evt_idle, vol, NULL);
e0eb2f82 128 res = snd_mixer_handle_events(vol->mixer);
0fdd5153
HJYP
129 }
130
f0861dee 131 if (cond & G_IO_IN)
55ccbddf
HJYP
132 {
133 /* the status of mixer is changed. update of display is needed. */
f0861dee 134 volumealsa_update_display(vol);
55ccbddf 135 }
55ccbddf 136
e0eb2f82 137 if ((cond & G_IO_HUP) || (res < 0))
f0861dee 138 {
139 /* This means there're some problems with alsa. */
06e29ce1 140 g_warning("volumealsa: ALSA (or pulseaudio) had a problem: "
e0eb2f82 141 "volumealsa: snd_mixer_handle_events() = %d,"
06e29ce1 142 " cond 0x%x (IN: 0x%x, HUP: 0x%x).", res, cond,
e0eb2f82 143 G_IO_IN, G_IO_HUP);
b285684e 144 gtk_widget_set_tooltip_text(vol->plugin, "ALSA (or pulseaudio) had a problem."
e0eb2f82 145 " Please check the lxpanel logs.");
ad987a0c 146
b285684e
AG
147 if (vol->restart_idle == 0)
148 vol->restart_idle = g_timeout_add_seconds(1, asound_restart, vol);
ad987a0c 149
55ccbddf
HJYP
150 return FALSE;
151 }
152
55ccbddf
HJYP
153 return TRUE;
154}
155
ad987a0c
HG
156static gboolean asound_restart(gpointer vol_gpointer)
157{
158 VolumeALSAPlugin * vol = vol_gpointer;
159
b285684e
AG
160 if (g_source_is_destroyed(g_main_current_source()))
161 return FALSE;
162
ad987a0c
HG
163 asound_deinitialize(vol);
164
165 if (!asound_initialize(vol)) {
06e29ce1 166 g_warning("volumealsa: Re-initialization failed.");
ad987a0c
HG
167 return TRUE; // try again in a second
168 }
169
06e29ce1 170 g_warning("volumealsa: Restarted ALSA interface...");
ad987a0c 171
b285684e 172 vol->restart_idle = 0;
ad987a0c
HG
173 return FALSE;
174}
175
f0861dee 176/* Initialize the ALSA interface. */
177static gboolean asound_initialize(VolumeALSAPlugin * vol)
49db0a37 178{
f0861dee 179 /* Access the "default" device. */
4542c20d
HJYP
180 snd_mixer_selem_id_alloca(&vol->sid);
181 snd_mixer_open(&vol->mixer, 0);
182 snd_mixer_attach(vol->mixer, "default");
183 snd_mixer_selem_register(vol->mixer, NULL, NULL);
184 snd_mixer_load(vol->mixer);
185
60d8abbb
MJ
186 /* Find Master element, or Front element, or PCM element, or LineOut element.
187 * If one of these succeeds, master_element is valid. */
f0861dee 188 if ( ! asound_find_element(vol, "Master"))
189 if ( ! asound_find_element(vol, "Front"))
190 if ( ! asound_find_element(vol, "PCM"))
b285684e 191 if ( ! asound_find_element(vol, "LineOut"))
e5f6c697 192 return FALSE;
4542c20d 193
f0861dee 194 /* Set the playback volume range as we wish it. */
4542c20d 195 snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);
9864f98f 196
f0861dee 197 /* Listen to events from ALSA. */
198 int n_fds = snd_mixer_poll_descriptors_count(vol->mixer);
199 struct pollfd * fds = g_new0(struct pollfd, n_fds);
55ccbddf 200
ad987a0c
HG
201 vol->channels = g_new0(GIOChannel *, n_fds);
202 vol->num_channels = n_fds;
203
f0861dee 204 snd_mixer_poll_descriptors(vol->mixer, fds, n_fds);
205 int i;
206 for (i = 0; i < n_fds; ++i)
55ccbddf 207 {
f0861dee 208 GIOChannel* channel = g_io_channel_unix_new(fds[i].fd);
209 g_io_add_watch(channel, G_IO_IN | G_IO_HUP, asound_mixer_event, vol);
ad987a0c 210 vol->channels[i] = channel;
55ccbddf 211 }
f0861dee 212 g_free(fds);
9864f98f 213 return TRUE;
49db0a37
FC
214}
215
ad987a0c
HG
216static void asound_deinitialize(VolumeALSAPlugin * vol)
217{
fbe0300a 218 guint i;
ad987a0c 219
08a8293e 220 if (vol->mixer_evt_idle != 0) {
ad987a0c 221 g_source_remove(vol->mixer_evt_idle);
08a8293e
HG
222 vol->mixer_evt_idle = 0;
223 }
ad987a0c
HG
224
225 for (i = 0; i < vol->num_channels; i++) {
226 g_io_channel_shutdown(vol->channels[i], FALSE, NULL);
227 g_io_channel_unref(vol->channels[i]);
228 }
229 g_free(vol->channels);
ad987a0c
HG
230 vol->channels = NULL;
231 vol->num_channels = 0;
08a8293e
HG
232
233 snd_mixer_close(vol->mixer);
234 vol->master_element = NULL;
b285684e 235 /* FIXME: unalloc vol->sid */
ad987a0c
HG
236}
237
f0861dee 238/* Get the presence of the mute control from the sound system. */
239static gboolean asound_has_mute(VolumeALSAPlugin * vol)
240{
60d8abbb 241 return ((vol->master_element != NULL) ? snd_mixer_selem_has_playback_switch(vol->master_element) : FALSE);
f0861dee 242}
243
244/* Get the condition of the mute control from the sound system. */
245static gboolean asound_is_muted(VolumeALSAPlugin * vol)
246{
247 /* The switch is on if sound is not muted, and off if the sound is muted.
248 * Initialize so that the sound appears unmuted if the control does not exist. */
249 int value = 1;
60d8abbb
MJ
250 if (vol->master_element != NULL)
251 snd_mixer_selem_get_playback_switch(vol->master_element, 0, &value);
f0861dee 252 return (value == 0);
253}
254
255/* Get the volume from the sound system.
256 * This implementation returns the average of the Front Left and Front Right channels. */
257static int asound_get_volume(VolumeALSAPlugin * vol)
49db0a37 258{
60d8abbb
MJ
259 long aleft = 0;
260 long aright = 0;
261 if (vol->master_element != NULL)
262 {
263 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
264 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
265 }
4542c20d 266 return (aleft + aright) >> 1;
49db0a37
FC
267}
268
f0861dee 269/* Set the volume to the sound system.
270 * This implementation sets the Front Left and Front Right channels to the specified value. */
271static void asound_set_volume(VolumeALSAPlugin * vol, int volume)
49db0a37 272{
60d8abbb
MJ
273 if (vol->master_element != NULL)
274 {
275 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
276 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
277 }
49db0a37
FC
278}
279
f0861dee 280/*** Graphics ***/
281
7192ab49 282static void volumealsa_update_current_icon(VolumeALSAPlugin * vol)
49db0a37 283{
f0861dee 284 /* Mute status. */
285 gboolean mute = asound_is_muted(vol);
4896be7f 286 int level = asound_get_volume(vol);
bfc11b5e 287
4896be7f
JL
288 /* Change icon according to mute / volume */
289 const char* icon="audio-volume-muted";
9a82da9a 290 const char* icon_panel="audio-volume-muted-panel";
4896be7f
JL
291 const char* icon_fallback=ICONS_MUTE;
292 if (mute)
293 {
9a82da9a 294 icon_panel = "audio-volume-muted-panel";
4896be7f
JL
295 icon="audio-volume-muted";
296 icon_fallback=ICONS_MUTE;
297 }
298 else if (level >= 75)
c14620f2 299 {
9a82da9a 300 icon_panel = "audio-volume-high-panel";
4896be7f
JL
301 icon="audio-volume-high";
302 icon_fallback=ICONS_VOLUME_HIGH;
303 }
304 else if (level >= 50)
305 {
9a82da9a 306 icon_panel = "audio-volume-medium-panel";
4896be7f
JL
307 icon="audio-volume-medium";
308 icon_fallback=ICONS_VOLUME_MEDIUM;
309 }
310 else if (level > 0)
311 {
9a82da9a 312 icon_panel = "audio-volume-low-panel";
4896be7f
JL
313 icon="audio-volume-low";
314 icon_fallback=ICONS_VOLUME_LOW;
315 }
316
7192ab49
JL
317 vol->icon_panel = icon_panel;
318 vol->icon = icon;
319 vol->icon_fallback= icon_fallback;
320}
321
322/* Do a full redraw of the display. */
323static void volumealsa_update_display(VolumeALSAPlugin * vol)
324{
325 /* Mute status. */
326 gboolean mute = asound_is_muted(vol);
327 int level = asound_get_volume(vol);
328
329 volumealsa_update_current_icon(vol);
330
4896be7f 331 /* Change icon, fallback to default icon if theme doesn't exsit */
a7bd16a4 332 if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon_panel))
4896be7f 333 {
a7bd16a4 334 if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon))
9a82da9a 335 {
a7bd16a4 336 lxpanel_image_set_from_file(vol->panel, vol->tray_icon, vol->icon_fallback);
9a82da9a 337 }
c14620f2 338 }
f0861dee 339
340 g_signal_handler_block(vol->mute_check, vol->mute_check_handler);
341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), mute);
342 gtk_widget_set_sensitive(vol->mute_check, asound_has_mute(vol));
343 g_signal_handler_unblock(vol->mute_check, vol->mute_check_handler);
344
345 /* Volume. */
f0861dee 346 if (vol->volume_scale != NULL)
347 {
348 g_signal_handler_block(vol->volume_scale, vol->volume_scale_handler);
349 gtk_range_set_value(GTK_RANGE(vol->volume_scale), asound_get_volume(vol));
350 g_signal_handler_unblock(vol->volume_scale, vol->volume_scale_handler);
351 }
352
353 /* Display current level in tooltip. */
354 char * tooltip = g_strdup_printf("%s %d", _("Volume control"), level);
b285684e 355 gtk_widget_set_tooltip_text(vol->plugin, tooltip);
f0861dee 356 g_free(tooltip);
49db0a37
FC
357}
358
7192ab49 359
f0861dee 360/* Handler for "button-press-event" signal on main widget. */
a7bd16a4 361static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * panel)
49db0a37 362{
1639d6b3 363 VolumeALSAPlugin * vol = lxpanel_plugin_get_data(widget);
b285684e 364
f0861dee 365 /* Left-click. Show or hide the popup window. */
d220e22a 366 if (event->button == 1)
367 {
f0861dee 368 if (vol->show_popup)
d220e22a 369 {
f0861dee 370 gtk_widget_hide(vol->popup_window);
371 vol->show_popup = FALSE;
d220e22a 372 }
373 else
374 {
f0861dee 375 gtk_widget_show_all(vol->popup_window);
376 vol->show_popup = TRUE;
d220e22a 377 }
378 }
f0861dee 379
380 /* Middle-click. Toggle the mute status. */
d220e22a 381 else if (event->button == 2)
382 {
383 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), ! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check)));
4542c20d 384 }
3e7b8eb7 385 return TRUE;
49db0a37
FC
386}
387
f0861dee 388/* Handler for "focus-out" signal on popup window. */
389static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event, VolumeALSAPlugin * vol)
49db0a37 390{
f0861dee 391 /* Hide the widget. */
392 gtk_widget_hide(vol->popup_window);
393 vol->show_popup = FALSE;
394 return FALSE;
49db0a37
FC
395}
396
4ec924bc
MJ
397/* Handler for "map" signal on popup window. */
398static void volumealsa_popup_map(GtkWidget * widget, VolumeALSAPlugin * vol)
399{
b285684e 400 lxpanel_plugin_adjust_popup_position(widget, vol->plugin);
4ec924bc
MJ
401}
402
7192ab49
JL
403static void volumealsa_theme_change(GtkWidget * widget, VolumeALSAPlugin * vol)
404{
a7bd16a4 405 if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon_panel))
7192ab49 406 {
a7bd16a4 407 if ( ! lxpanel_image_set_icon_theme(vol->panel, vol->tray_icon, vol->icon))
7192ab49 408 {
a7bd16a4 409 lxpanel_image_set_from_file(vol->panel, vol->tray_icon, vol->icon_fallback);
7192ab49
JL
410 }
411 }
412}
413
f0861dee 414/* Handler for "value_changed" signal on popup window vertical scale. */
415static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol)
0fdd5153 416{
f0861dee 417 /* Reflect the value of the control to the sound system. */
418 asound_set_volume(vol, gtk_range_get_value(range));
419
420 /* Redraw the controls. */
421 volumealsa_update_display(vol);
422}
423
424/* Handler for "scroll-event" signal on popup window vertical scale. */
425static void volumealsa_popup_scale_scrolled(GtkScale * scale, GdkEventScroll * evt, VolumeALSAPlugin * vol)
426{
427 /* Get the state of the vertical scale. */
428 gdouble val = gtk_range_get_value(GTK_RANGE(vol->volume_scale));
429
430 /* Dispatch on scroll direction to update the value. */
431 if ((evt->direction == GDK_SCROLL_UP) || (evt->direction == GDK_SCROLL_LEFT))
0fdd5153 432 val += 2;
f0861dee 433 else
0fdd5153 434 val -= 2;
f0861dee 435
436 /* Reset the state of the vertical scale. This provokes a "value_changed" event. */
437 gtk_range_set_value(GTK_RANGE(vol->volume_scale), CLAMP((int)val, 0, 100));
0fdd5153
HJYP
438}
439
f0861dee 440/* Handler for "toggled" signal on popup window mute checkbox. */
441static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin * vol)
49db0a37 442{
f0861dee 443 /* Get the state of the mute toggle. */
444 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
445
446 /* Reflect the mute toggle to the sound system. */
60d8abbb
MJ
447 if (vol->master_element != NULL)
448 {
449 int chn;
450 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
451 snd_mixer_selem_set_playback_switch(vol->master_element, chn, ((active) ? 0 : 1));
452 }
4542c20d 453
f0861dee 454 /* Redraw the controls. */
455 volumealsa_update_display(vol);
49db0a37
FC
456}
457
f0861dee 458/* Build the window that appears when the top level widget is clicked. */
b285684e 459static void volumealsa_build_popup_window(GtkWidget *p)
49db0a37 460{
b285684e 461 VolumeALSAPlugin * vol = lxpanel_plugin_get_data(p);
f0861dee 462
463 /* Create a new window. */
1918164d 464 vol->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
f0861dee 465 gtk_window_set_decorated(GTK_WINDOW(vol->popup_window), FALSE);
466 gtk_container_set_border_width(GTK_CONTAINER(vol->popup_window), 5);
467 gtk_window_set_default_size(GTK_WINDOW(vol->popup_window), 80, 140);
468 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->popup_window), TRUE);
469 gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->popup_window), TRUE);
91f2b215 470 gtk_window_set_type_hint(GTK_WINDOW(vol->popup_window), GDK_WINDOW_TYPE_HINT_UTILITY);
f0861dee 471
4ec924bc 472 /* Connect signals. */
93a217b3 473 g_signal_connect(G_OBJECT(vol->popup_window), "focus-out-event", G_CALLBACK(volumealsa_popup_focus_out), vol);
4ec924bc 474 g_signal_connect(G_OBJECT(vol->popup_window), "map", G_CALLBACK(volumealsa_popup_map), vol);
f0861dee 475
476 /* Create a scrolled window as the child of the top level window. */
477 GtkWidget * scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
478 gtk_container_set_border_width (GTK_CONTAINER(scrolledwindow), 0);
479 gtk_widget_show(scrolledwindow);
480 gtk_container_add(GTK_CONTAINER(vol->popup_window), scrolledwindow);
09fa171b 481 gtk_widget_set_can_focus(scrolledwindow, FALSE);
4542c20d 482 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
63de7afd 483 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_NONE);
4542c20d 484
f0861dee 485 /* Create a viewport as the child of the scrolled window. */
486 GtkWidget * viewport = gtk_viewport_new(NULL, NULL);
487 gtk_container_add(GTK_CONTAINER(scrolledwindow), viewport);
488 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
4542c20d
HJYP
489 gtk_widget_show(viewport);
490
f0861dee 491 /* Create a frame as the child of the viewport. */
492 GtkWidget * frame = gtk_frame_new(_("Volume"));
4542c20d
HJYP
493 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
494 gtk_container_add(GTK_CONTAINER(viewport), frame);
495
f0861dee 496 /* Create a vertical box as the child of the frame. */
497 GtkWidget * box = gtk_vbox_new(FALSE, 0);
498 gtk_container_add(GTK_CONTAINER(frame), box);
4542c20d 499
f0861dee 500 /* Create a vertical scale as the child of the vertical box. */
501 vol->volume_scale = gtk_vscale_new(GTK_ADJUSTMENT(gtk_adjustment_new(100, 0, 100, 0, 0, 0)));
502 gtk_scale_set_draw_value(GTK_SCALE(vol->volume_scale), FALSE);
503 gtk_range_set_inverted(GTK_RANGE(vol->volume_scale), TRUE);
504 gtk_box_pack_start(GTK_BOX(box), vol->volume_scale, TRUE, TRUE, 0);
4542c20d 505
f0861dee 506 /* Value-changed and scroll-event signals. */
93a217b3 507 vol->volume_scale_handler = g_signal_connect(vol->volume_scale, "value-changed", G_CALLBACK(volumealsa_popup_scale_changed), vol);
f0861dee 508 g_signal_connect(vol->volume_scale, "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol);
49db0a37 509
f0861dee 510 /* Create a check button as the child of the vertical box. */
0fdd5153 511 vol->mute_check = gtk_check_button_new_with_label(_("Mute"));
0fdd5153 512 gtk_box_pack_end(GTK_BOX(box), vol->mute_check, FALSE, FALSE, 0);
f0861dee 513 vol->mute_check_handler = g_signal_connect(vol->mute_check, "toggled", G_CALLBACK(volumealsa_popup_mute_toggled), vol);
49db0a37 514
f0861dee 515 /* Set background to default. */
b285684e 516 gtk_widget_set_style(viewport, panel_get_defstyle(vol->panel));
49db0a37
FC
517}
518
f0861dee 519/* Plugin constructor. */
a7bd16a4 520static GtkWidget *volumealsa_constructor(LXPanel *panel, config_setting_t *settings)
49db0a37 521{
f0861dee 522 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
523 VolumeALSAPlugin * vol = g_new0(VolumeALSAPlugin, 1);
b285684e 524 GtkWidget *p;
4542c20d 525
f0861dee 526 /* Initialize ALSA. If that fails, present nothing. */
527 if ( ! asound_initialize(vol))
b285684e
AG
528 {
529 volumealsa_destructor(vol);
530 return NULL;
531 }
4542c20d 532
f0861dee 533 /* Allocate top level widget and set into Plugin widget pointer. */
b285684e
AG
534 vol->panel = panel;
535 vol->plugin = p = gtk_event_box_new();
72312227 536 vol->settings = settings;
b285684e
AG
537 lxpanel_plugin_set_data(p, vol, volumealsa_destructor);
538 gtk_widget_add_events(p, GDK_BUTTON_PRESS_MASK);
539 gtk_widget_set_tooltip_text(p, _("Volume control"));
4542c20d 540
f0861dee 541 /* Allocate icon as a child of top level. */
542 vol->tray_icon = gtk_image_new();
b285684e 543 gtk_container_add(GTK_CONTAINER(p), vol->tray_icon);
4542c20d 544
f0861dee 545 /* Initialize window to appear when icon clicked. */
546 volumealsa_build_popup_window(p);
49db0a37 547
f0861dee 548 /* Connect signals. */
b285684e
AG
549 g_signal_connect(G_OBJECT(p), "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol );
550 g_signal_connect(panel_get_icon_theme(panel), "changed", G_CALLBACK(volumealsa_theme_change), vol );
49db0a37 551
f0861dee 552 /* Update the display, show the widget, and return. */
553 volumealsa_update_display(vol);
b285684e
AG
554 gtk_widget_show_all(p);
555 return p;
f0861dee 556}
1abc8a6a 557
f0861dee 558/* Plugin destructor. */
b285684e 559static void volumealsa_destructor(gpointer user_data)
f0861dee 560{
b285684e 561 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) user_data;
49db0a37 562
ad987a0c 563 asound_deinitialize(vol);
4542c20d 564
f0861dee 565 /* If the dialog box is open, dismiss it. */
566 if (vol->popup_window != NULL)
567 gtk_widget_destroy(vol->popup_window);
49db0a37 568
b285684e
AG
569 if (vol->restart_idle)
570 g_source_remove(vol->restart_idle);
571
f0861dee 572 /* Deallocate all memory. */
573 g_free(vol);
49db0a37
FC
574}
575
552475ec
JL
576/* Callback when the configuration dialog is to be shown. */
577
752ee4e2 578static GtkWidget *volumealsa_configure(LXPanel *panel, GtkWidget *p)
552475ec 579{
72312227
AG
580 VolumeALSAPlugin * vol = lxpanel_plugin_get_data(p);
581 char *path = NULL;
552475ec
JL
582 const gchar *command_line = NULL;
583
72312227
AG
584 /* FIXME: configure settings! */
585 /* check if command line was configured */
586 config_setting_lookup_string(vol->settings, "MixerCommand", &command_line);
587
588 /* if command isn't set in settings then let guess it */
589 if (command_line == NULL && (path = g_find_program_in_path("pulseaudio")))
552475ec 590 {
72312227 591 g_free(path);
552475ec 592 /* Assume that when pulseaudio is installed, it's launching every time */
72312227 593 if ((path = g_find_program_in_path("gnome-sound-applet")))
552475ec
JL
594 {
595 command_line = "gnome-sound-applet";
596 }
72312227 597 else if ((path = g_find_program_in_path("pavucontrol")))
552475ec 598 {
72312227 599 command_line = "pavucontrol";
552475ec
JL
600 }
601 }
602
603 /* Fallback to alsamixer when PA is not running, or when no PA utility is find */
604 if (command_line == NULL)
605 {
72312227 606 if ((path = g_find_program_in_path("gnome-alsamixer")))
552475ec
JL
607 {
608 command_line = "gnome-alsamixer";
609 }
72312227
AG
610 else if ((path = g_find_program_in_path("alsamixergui")))
611 {
612 command_line = "alsamixergui";
613 }
614 else if ((path = g_find_program_in_path("alsamixer")))
552475ec 615 {
72312227
AG
616 g_free(path);
617 if ((path = g_find_program_in_path("xterm")))
552475ec 618 {
72312227 619 command_line = "xterm -e alsamixer";
552475ec
JL
620 }
621 }
622 }
72312227 623 g_free(path);
552475ec
JL
624
625 if (command_line)
626 {
09fa171b
AG
627 fm_launch_command_simple(NULL, NULL, G_APP_INFO_CREATE_NONE,
628 command_line, NULL);
552475ec
JL
629 }
630 else
631 {
c065f0b3
AG
632 fm_show_error(NULL, NULL,
633 _("Error, you need to install an application to configure"
634 " the sound (pavucontrol, alsamixer ...)"));
552475ec
JL
635 }
636
b285684e 637 return NULL;
552475ec
JL
638}
639
8f9e6256 640/* Callback when panel configuration changes. */
a7bd16a4 641static void volumealsa_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
8f9e6256 642{
643 /* Do a full redraw. */
b285684e 644 volumealsa_update_display(lxpanel_plugin_get_data(p));
8f9e6256 645}
49db0a37 646
b285684e 647FM_DEFINE_MODULE(lxpanel_gtk, volumealsa)
2918994e 648
b285684e
AG
649/* Plugin descriptor. */
650LXPanelPluginInit fm_module_init_lxpanel_gtk = {
3c3e9c9e 651 .name = N_("Volume Control"),
a04979c2 652 .description = N_("Display and control volume for ALSA"),
3c3e9c9e 653
b285684e 654 .new_instance = volumealsa_constructor,
a04979c2 655 .config = volumealsa_configure,
b285684e
AG
656 .reconfigure = volumealsa_panel_configuration_changed,
657 .button_press_event = volumealsa_button_press_event
49db0a37 658};
e0eb2f82
HG
659
660/* vim: set sw=4 et sts=4 : */