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