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