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