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