Adding upstream version 0.9.1.
[debian/lxpanel.git] / plugins / volumealsa / volumealsa.c
CommitLineData
0688b017 1/*
7a1c5048
AG
2 * Copyright (C) 2006-2008 Jim Huang <jserv.tw@gmail.com>
3 * 2006 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 * 2008 Frank ENDRES <frank_endres@yahoo.fr>
5 *
0688b017
AG
6 * Copyright (C) 2008 Fred Chien <fred@lxde.org>
7 * 2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
8 * 2009-2010 Marty Jack <martyj19@comcast.net>
9 * 2010-2012 Julien Lavergne <julien.lavergne@gmail.com>
10 * 2012 Henry Gebhardt <hsggebhardt@gmail.com>
11 * 2014 Peter <ombalaxitabou@users.sf.net>
7a1c5048 12 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
0688b017
AG
13 *
14 * This file is a part of LXPanel project.
6cc5e1a6
DB
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software Foundation,
28 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 */
30
f7ecd6ce
AG
31#define _ISOC99_SOURCE /* lrint() */
32#define _GNU_SOURCE /* exp10() */
33
7a1c5048
AG
34#ifdef HAVE_CONFIG_H
35#include <config.h>
36#endif
37
6cc5e1a6 38#include <gtk/gtk.h>
7a1c5048 39#include <gdk/gdkkeysyms.h>
6cc5e1a6
DB
40#include <stdlib.h>
41#include <fcntl.h>
42#include <unistd.h>
43#include <glib.h>
44#include <glib/gi18n.h>
45#include <gdk-pixbuf/gdk-pixbuf.h>
7a1c5048
AG
46#ifdef DISABLE_ALSA
47#include <sys/types.h>
48#include <sys/stat.h>
49#include <sys/ioctl.h>
50#include <string.h>
51#include <stdio.h>
52#include <fcntl.h>
53#include <errno.h>
54#ifdef HAVE_SYS_SOUNDCARD_H
55#include <sys/soundcard.h>
56#elif defined(HAVE_LINUX_SOUNDCARD_H)
57#include <linux/soundcard.h>
58#else
59#error "Not supported platform"
60#endif
61#ifndef SOUND_MIXER_PHONEOUT
62# define SOUND_MIXER_PHONEOUT SOUND_MIXER_MONO
63#endif
64//TODO: support OSSv4
65#else
6cc5e1a6 66#include <alsa/asoundlib.h>
96dc8a45 67#include <poll.h>
7a1c5048 68#endif
f7ecd6ce 69#include <math.h>
6b775dbb 70#include <libfm/fm-gtk.h>
7a1c5048 71
6cc5e1a6 72#include "plugin.h"
f7ecd6ce 73#include "misc.h"
7a1c5048 74#include "gtk-compat.h"
f7ecd6ce
AG
75
76#define ICONS_VOLUME_HIGH "volume-high"
77#define ICONS_VOLUME_MEDIUM "volume-medium"
78#define ICONS_VOLUME_LOW "volume-low"
79#define ICONS_MUTE "mute"
80
81#ifdef __UCLIBC__
82/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */
83# define M_LN10 2.30258509299404568402 /* log_e 10 */
84#define exp10(x) (exp((x) * log(10)))
85#endif /* __UCLIBC__ */
6cc5e1a6 86
f7ecd6ce 87#define MAX_LINEAR_DB_SCALE 24
6cc5e1a6 88
7a1c5048
AG
89#ifdef DISABLE_ALSA
90typedef union
91{
92 struct
93 {
94 unsigned char left;
95 unsigned char right;
96 };
97 int value;
98} StereoVolume;
99#endif
100
6cc5e1a6 101typedef struct {
2ba86315
DB
102
103 /* Graphics. */
6b775dbb
AG
104 GtkWidget * plugin; /* Back pointer to the widget */
105 LXPanel * panel; /* Back pointer to panel */
19ab5cea 106 config_setting_t * settings; /* Plugin settings */
2ba86315
DB
107 GtkWidget * tray_icon; /* Displayed image */
108 GtkWidget * popup_window; /* Top level window for popup */
109 GtkWidget * volume_scale; /* Scale for volume */
110 GtkWidget * mute_check; /* Checkbox for mute state */
111 gboolean show_popup; /* Toggle to show and hide the popup on left click */
112 guint volume_scale_handler; /* Handler for vscale widget */
113 guint mute_check_handler; /* Handler for mute_check widget */
114
7a1c5048
AG
115#ifdef DISABLE_ALSA
116 int mixer_fd; /* The mixer FD */
117 gdouble vol_before_mute; /* Save value when muted */
118
119 guint master_channel;
120#else
2ba86315
DB
121 /* ALSA interface. */
122 snd_mixer_t * mixer; /* The mixer */
2ba86315
DB
123 snd_mixer_elem_t * master_element; /* The Master element */
124 guint mixer_evt_idle; /* Timer to handle restarting poll */
6b775dbb 125 guint restart_idle;
f7ecd6ce 126 gint alsamixer_mapping;
eea54180
DB
127
128 /* unloading and error handling */
129 GIOChannel **channels; /* Channels that we listen to */
19ab5cea 130 guint *watches; /* Watcher IDs for channels */
eea54180 131 guint num_channels; /* Number of channels */
aaccad27 132
7a1c5048
AG
133 gint used_device;
134 char *master_channel;
135#endif
136
aaccad27 137 /* Icons */
aaccad27
AL
138 const char* icon_panel;
139 const char* icon_fallback;
140
7a1c5048
AG
141 /* Clicks */
142 int mute_click;
143 GdkModifierType mute_click_mods;
144 int mixer_click;
145 GdkModifierType mixer_click_mods;
146 int slider_click;
147 GdkModifierType slider_click_mods;
148
149 /* Hotkeys */
150 char * hotkey_up;
151 char * hotkey_down;
152 char * hotkey_mute;
153
154 GtkWidget *channel_selector; /* Used by configure dialog */
2ba86315
DB
155} VolumeALSAPlugin;
156
7a1c5048 157#ifndef DISABLE_ALSA
eea54180 158static gboolean asound_restart(gpointer vol_gpointer);
7a1c5048 159#endif
2ba86315 160static gboolean asound_initialize(VolumeALSAPlugin * vol);
eea54180 161static void asound_deinitialize(VolumeALSAPlugin * vol);
2ba86315 162static void volumealsa_update_display(VolumeALSAPlugin * vol);
6b775dbb 163static void volumealsa_destructor(gpointer user_data);
2ba86315
DB
164
165/*** ALSA ***/
166
7a1c5048
AG
167#ifndef DISABLE_ALSA
168static gboolean asound_find_element(VolumeALSAPlugin * vol, const char ** ename, int n)
6cc5e1a6 169{
7a1c5048
AG
170 int i;
171 snd_mixer_selem_id_t * sid; /* The element ID */
172
173 snd_mixer_selem_id_alloca(&sid);
174 for (i = 0; i < n; i++)
2ba86315 175 {
7a1c5048
AG
176 for (vol->master_element = snd_mixer_first_elem(vol->mixer);
177 vol->master_element != NULL;
178 vol->master_element = snd_mixer_elem_next(vol->master_element))
179 {
180 snd_mixer_selem_get_id(vol->master_element, sid);
181 if (snd_mixer_selem_is_active(vol->master_element) &&
182 strcmp(ename[i], snd_mixer_selem_id_get_name(sid)) == 0)
183 return TRUE;
184 }
6cc5e1a6 185 }
6cc5e1a6
DB
186 return FALSE;
187}
188
96dc8a45
DB
189/* NOTE by PCMan:
190 * This is magic! Since ALSA uses its own machanism to handle this part.
191 * After polling of mixer fds, it requires that we should call
192 * snd_mixer_handle_events to clear all pending mixer events.
193 * However, when using the glib IO channels approach, we don't have
194 * poll() and snd_mixer_poll_descriptors_revents(). Due to the design of
195 * glib, on_mixer_event() will be called for every fd whose status was
196 * changed. So, after each poll(), it's called for several times,
197 * not just once. Therefore, we cannot call snd_mixer_handle_events()
198 * directly in the event handler. Otherwise, it will get called for
199 * several times, which might clear unprocessed pending events in the queue.
200 * So, here we call it once in the event callback for the first fd.
201 * Then, we don't call it for the following fds. After all fds with changed
202 * status are handled, we remove this restriction in an idle handler.
203 * The next time the event callback is involked for the first fs, we can
204 * call snd_mixer_handle_events() again. Racing shouldn't happen here
205 * because the idle handler has the same priority as the io channel callback.
206 * So, io callbacks for future pending events should be in the next gmain
207 * iteration, and won't be affected.
208 */
2ba86315
DB
209
210static gboolean asound_reset_mixer_evt_idle(VolumeALSAPlugin * vol)
96dc8a45 211{
6b775dbb
AG
212 if (!g_source_is_destroyed(g_main_current_source()))
213 vol->mixer_evt_idle = 0;
96dc8a45
DB
214 return FALSE;
215}
216
2ba86315
DB
217/* Handler for I/O event on ALSA channel. */
218static gboolean asound_mixer_event(GIOChannel * channel, GIOCondition cond, gpointer vol_gpointer)
96dc8a45 219{
2ba86315 220 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) vol_gpointer;
eea54180 221 int res = 0;
2ba86315 222
19ab5cea
AG
223 if (g_source_is_destroyed(g_main_current_source()))
224 return FALSE;
225
2ba86315 226 if (vol->mixer_evt_idle == 0)
96dc8a45 227 {
2ba86315 228 vol->mixer_evt_idle = g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc) asound_reset_mixer_evt_idle, vol, NULL);
eea54180 229 res = snd_mixer_handle_events(vol->mixer);
96dc8a45
DB
230 }
231
2ba86315 232 if (cond & G_IO_IN)
96dc8a45
DB
233 {
234 /* the status of mixer is changed. update of display is needed. */
2ba86315 235 volumealsa_update_display(vol);
96dc8a45 236 }
96dc8a45 237
eea54180 238 if ((cond & G_IO_HUP) || (res < 0))
2ba86315
DB
239 {
240 /* This means there're some problems with alsa. */
6b775dbb 241 g_warning("volumealsa: ALSA (or pulseaudio) had a problem: "
eea54180 242 "volumealsa: snd_mixer_handle_events() = %d,"
6b775dbb 243 " cond 0x%x (IN: 0x%x, HUP: 0x%x).", res, cond,
eea54180 244 G_IO_IN, G_IO_HUP);
7a1c5048
AG
245 gtk_widget_set_tooltip_text(vol->plugin, _("ALSA (or pulseaudio) had a problem."
246 " Please check the lxpanel logs."));
eea54180 247
6b775dbb
AG
248 if (vol->restart_idle == 0)
249 vol->restart_idle = g_timeout_add_seconds(1, asound_restart, vol);
eea54180 250
96dc8a45
DB
251 return FALSE;
252 }
253
254 return TRUE;
255}
256
eea54180
DB
257static gboolean asound_restart(gpointer vol_gpointer)
258{
259 VolumeALSAPlugin * vol = vol_gpointer;
260
6b775dbb
AG
261 if (g_source_is_destroyed(g_main_current_source()))
262 return FALSE;
263
eea54180
DB
264 asound_deinitialize(vol);
265
266 if (!asound_initialize(vol)) {
6b775dbb 267 g_warning("volumealsa: Re-initialization failed.");
eea54180
DB
268 return TRUE; // try again in a second
269 }
270
6b775dbb 271 g_warning("volumealsa: Restarted ALSA interface...");
eea54180 272
6b775dbb 273 vol->restart_idle = 0;
eea54180
DB
274 return FALSE;
275}
7a1c5048 276#endif
eea54180 277
2ba86315
DB
278/* Initialize the ALSA interface. */
279static gboolean asound_initialize(VolumeALSAPlugin * vol)
6cc5e1a6 280{
7a1c5048 281#ifdef DISABLE_ALSA
2ba86315 282 /* Access the "default" device. */
7a1c5048
AG
283 vol->mixer_fd = open ("/dev/mixer", O_RDWR, 0);
284 if (vol->mixer_fd < 0)
285 {
286 g_warning("cannot initialize OSS mixer: %s", strerror(errno));
287 return FALSE;
288 }
289
290 //FIXME: is there a way to watch volume with OSS?
291#else
6cc5e1a6 292 snd_mixer_open(&vol->mixer, 0);
7a1c5048
AG
293 if (vol->used_device < 0)
294 snd_mixer_attach(vol->mixer, "default");
295 else
296 {
297 char id[16];
298
299 snprintf(id, sizeof(id), "hw:%d", vol->used_device);
300 snd_mixer_attach(vol->mixer, id);
301 }
6cc5e1a6
DB
302 snd_mixer_selem_register(vol->mixer, NULL, NULL);
303 snd_mixer_load(vol->mixer);
304
7a1c5048
AG
305 if (vol->master_channel)
306 {
307 /* If user defined the channel then use it */
308 if (!asound_find_element(vol, (const char **)&vol->master_channel, 1))
309 return FALSE;
310 }
311 else
312 {
313 const char * def_channels[] = { "Master", "Front", "PCM", "LineOut" };
4652f59b
DB
314 /* Find Master element, or Front element, or PCM element, or LineOut element.
315 * If one of these succeeds, master_element is valid. */
7a1c5048
AG
316 if (!asound_find_element(vol, def_channels, G_N_ELEMENTS(def_channels)))
317 {
318 /* Could not find any predefined, let choose any available */
319 for (vol->master_element = snd_mixer_first_elem(vol->mixer);
320 vol->master_element != NULL;
321 vol->master_element = snd_mixer_elem_next(vol->master_element))
322 {
323 if (snd_mixer_selem_is_active(vol->master_element) &&
324 snd_mixer_selem_has_playback_volume(vol->master_element) &&
325 !snd_mixer_selem_has_capture_volume(vol->master_element) &&
326 !snd_mixer_selem_has_capture_switch(vol->master_element))
327 break;
328 }
329 if (vol->master_element == NULL)
330 return FALSE;
331 }
332 }
6cc5e1a6 333
2ba86315 334 /* Set the playback volume range as we wish it. */
f7ecd6ce
AG
335 if ( ! vol->alsamixer_mapping)
336 snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);
6cc5e1a6 337
2ba86315
DB
338 /* Listen to events from ALSA. */
339 int n_fds = snd_mixer_poll_descriptors_count(vol->mixer);
340 struct pollfd * fds = g_new0(struct pollfd, n_fds);
96dc8a45 341
eea54180 342 vol->channels = g_new0(GIOChannel *, n_fds);
19ab5cea 343 vol->watches = g_new0(guint, n_fds);
eea54180
DB
344 vol->num_channels = n_fds;
345
2ba86315
DB
346 snd_mixer_poll_descriptors(vol->mixer, fds, n_fds);
347 int i;
348 for (i = 0; i < n_fds; ++i)
96dc8a45 349 {
2ba86315 350 GIOChannel* channel = g_io_channel_unix_new(fds[i].fd);
19ab5cea 351 vol->watches[i] = g_io_add_watch(channel, G_IO_IN | G_IO_HUP, asound_mixer_event, vol);
eea54180 352 vol->channels[i] = channel;
96dc8a45 353 }
2ba86315 354 g_free(fds);
7a1c5048 355#endif
6cc5e1a6
DB
356 return TRUE;
357}
358
eea54180
DB
359static void asound_deinitialize(VolumeALSAPlugin * vol)
360{
7a1c5048
AG
361#ifdef DISABLE_ALSA
362 if (vol->mixer_fd >= 0)
363 close(vol->mixer_fd);
364 vol->mixer_fd = -1;
365#else
6b775dbb 366 guint i;
eea54180 367
aaccad27 368 if (vol->mixer_evt_idle != 0) {
eea54180 369 g_source_remove(vol->mixer_evt_idle);
aaccad27
AL
370 vol->mixer_evt_idle = 0;
371 }
eea54180
DB
372
373 for (i = 0; i < vol->num_channels; i++) {
19ab5cea 374 g_source_remove(vol->watches[i]);
eea54180
DB
375 g_io_channel_shutdown(vol->channels[i], FALSE, NULL);
376 g_io_channel_unref(vol->channels[i]);
377 }
378 g_free(vol->channels);
19ab5cea 379 g_free(vol->watches);
eea54180 380 vol->channels = NULL;
19ab5cea 381 vol->watches = NULL;
eea54180 382 vol->num_channels = 0;
aaccad27 383
7a1c5048
AG
384 if (vol->mixer)
385 snd_mixer_close(vol->mixer);
386 vol->mixer = NULL;
aaccad27 387 vol->master_element = NULL;
7a1c5048 388#endif
eea54180
DB
389}
390
2ba86315
DB
391/* Get the presence of the mute control from the sound system. */
392static gboolean asound_has_mute(VolumeALSAPlugin * vol)
393{
7a1c5048
AG
394#ifdef DISABLE_ALSA
395 /* it's emulated with OSS */
396 return TRUE;
397#else
4652f59b 398 return ((vol->master_element != NULL) ? snd_mixer_selem_has_playback_switch(vol->master_element) : FALSE);
7a1c5048 399#endif
2ba86315
DB
400}
401
402/* Get the condition of the mute control from the sound system. */
403static gboolean asound_is_muted(VolumeALSAPlugin * vol)
6cc5e1a6 404{
2ba86315
DB
405 /* The switch is on if sound is not muted, and off if the sound is muted.
406 * Initialize so that the sound appears unmuted if the control does not exist. */
407 int value = 1;
7a1c5048
AG
408#ifdef DISABLE_ALSA
409 StereoVolume levels;
410
411 ioctl(vol->mixer_fd, MIXER_READ(vol->master_channel), &levels.value);
412 value = (levels.left + levels.right) >> 1;
413#else
4652f59b
DB
414 if (vol->master_element != NULL)
415 snd_mixer_selem_get_playback_switch(vol->master_element, 0, &value);
7a1c5048 416#endif
2ba86315
DB
417 return (value == 0);
418}
419
7a1c5048 420#ifndef DISABLE_ALSA
f7ecd6ce
AG
421static long lrint_dir(double x, int dir)
422{
423 if (dir > 0)
424 return lrint(ceil(x));
425 else if (dir < 0)
426 return lrint(floor(x));
427 else
428 return lrint(x);
429}
430
431static inline gboolean use_linear_dB_scale(long dBmin, long dBmax)
432{
433 return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
434}
435
436static long get_normalized_volume(snd_mixer_elem_t *elem,
437 snd_mixer_selem_channel_id_t channel)
438{
439 long min, max, value;
440 double normalized, min_norm;
441 int err;
442
443 err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max);
444 if (err < 0 || min >= max) {
445 err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
446 if (err < 0 || min == max)
447 return 0;
448
449 err = snd_mixer_selem_get_playback_volume(elem, channel, &value);
450 if (err < 0)
451 return 0;
452
453 return lrint(100.0 * (value - min) / (double)(max - min));
454 }
455
456 err = snd_mixer_selem_get_playback_dB(elem, channel, &value);
457 if (err < 0)
458 return 0;
459
460 if (use_linear_dB_scale(min, max))
461 return lrint(100.0 * (value - min) / (double)(max - min));
462
463 normalized = exp10((value - max) / 6000.0);
464 if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
465 min_norm = exp10((min - max) / 6000.0);
466 normalized = (normalized - min_norm) / (1 - min_norm);
467 }
468
469 return lrint(100.0 * normalized);
470}
7a1c5048 471#endif
f7ecd6ce 472
2ba86315
DB
473/* Get the volume from the sound system.
474 * This implementation returns the average of the Front Left and Front Right channels. */
475static int asound_get_volume(VolumeALSAPlugin * vol)
476{
7a1c5048
AG
477#ifdef DISABLE_ALSA
478 StereoVolume levels;
479
480 ioctl(vol->mixer_fd, MIXER_READ(vol->master_channel), &levels.value);
481 return (levels.left + levels.right) >> 1;
482#else
4652f59b
DB
483 long aleft = 0;
484 long aright = 0;
f7ecd6ce 485
4652f59b
DB
486 if (vol->master_element != NULL)
487 {
f7ecd6ce
AG
488 if ( ! vol->alsamixer_mapping)
489 {
490 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
491 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
492 }
493 else
494 {
495 aleft = get_normalized_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT);
496 aright = get_normalized_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT);
497 }
4652f59b 498 }
6cc5e1a6 499 return (aleft + aright) >> 1;
7a1c5048 500#endif
6cc5e1a6
DB
501}
502
7a1c5048 503#ifndef DISABLE_ALSA
f7ecd6ce
AG
504static int set_normalized_volume(snd_mixer_elem_t *elem,
505 snd_mixer_selem_channel_id_t channel,
506 int vol,
507 int dir)
508{
509 long min, max, value;
510 double min_norm, volume;
511 int err;
512
513 volume = vol / 100.0;
514
515 err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max);
516 if (err < 0 || min >= max) {
517 err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
518 if (err < 0)
519 return err;
520
521 value = lrint_dir(volume * (max - min), dir) + min;
522 return snd_mixer_selem_set_playback_volume(elem, channel, value);
523 }
524
525 if (use_linear_dB_scale(min, max)) {
526 value = lrint_dir(volume * (max - min), dir) + min;
527 return snd_mixer_selem_set_playback_dB(elem, channel, value, dir);
528 }
529
530 if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
531 min_norm = exp10((min - max) / 6000.0);
532 volume = volume * (1 - min_norm) + min_norm;
533 }
534 value = lrint_dir(6000.0 * log10(volume), dir) + max;
535
536 return snd_mixer_selem_set_playback_dB(elem, channel, value, dir);
537}
7a1c5048 538#endif
f7ecd6ce 539
2ba86315
DB
540/* Set the volume to the sound system.
541 * This implementation sets the Front Left and Front Right channels to the specified value. */
542static void asound_set_volume(VolumeALSAPlugin * vol, int volume)
6cc5e1a6 543{
f7ecd6ce
AG
544 int dir = volume - asound_get_volume(vol);
545
546 /* Volume is set to the correct value already */
547 if (dir == 0)
548 return;
549
7a1c5048
AG
550#ifdef DISABLE_ALSA
551 StereoVolume levels;
552
553 levels.left = levels.right = volume;
554 ioctl(vol->mixer_fd, MIXER_WRITE(vol->master_channel), &levels.value);
555#else
4652f59b
DB
556 if (vol->master_element != NULL)
557 {
f7ecd6ce
AG
558 if ( ! vol->alsamixer_mapping)
559 {
560 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
561 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
562 }
563 else
564 {
565 set_normalized_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume, dir);
566 set_normalized_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume, dir);
567 }
4652f59b 568 }
7a1c5048 569#endif
6cc5e1a6
DB
570}
571
2ba86315
DB
572/*** Graphics ***/
573
f7ecd6ce 574static void volumealsa_lookup_current_icon(VolumeALSAPlugin * vol, gboolean mute, int level)
6cc5e1a6 575{
514580cf 576 /* Change icon according to mute / volume */
514580cf
DB
577 const char* icon_panel="audio-volume-muted-panel";
578 const char* icon_fallback=ICONS_MUTE;
579 if (mute)
580 {
581 icon_panel = "audio-volume-muted-panel";
514580cf
DB
582 icon_fallback=ICONS_MUTE;
583 }
f7ecd6ce 584 else if (level >= 66)
514580cf
DB
585 {
586 icon_panel = "audio-volume-high-panel";
514580cf
DB
587 icon_fallback=ICONS_VOLUME_HIGH;
588 }
f7ecd6ce 589 else if (level >= 33)
514580cf
DB
590 {
591 icon_panel = "audio-volume-medium-panel";
514580cf
DB
592 icon_fallback=ICONS_VOLUME_MEDIUM;
593 }
594 else if (level > 0)
595 {
596 icon_panel = "audio-volume-low-panel";
514580cf
DB
597 icon_fallback=ICONS_VOLUME_LOW;
598 }
599
aaccad27 600 vol->icon_panel = icon_panel;
f7ecd6ce 601 vol->icon_fallback = icon_fallback;
aaccad27
AL
602}
603
f7ecd6ce 604static void volumealsa_update_current_icon(VolumeALSAPlugin * vol, gboolean mute, int level)
aaccad27 605{
f7ecd6ce
AG
606 /* Find suitable icon */
607 volumealsa_lookup_current_icon(vol, mute, level);
aaccad27 608
514580cf 609 /* Change icon, fallback to default icon if theme doesn't exsit */
f7ecd6ce 610 lxpanel_image_change_icon(vol->tray_icon, vol->icon_panel, vol->icon_fallback);
2ba86315 611
f7ecd6ce
AG
612 /* Display current level in tooltip. */
613 char * tooltip = g_strdup_printf("%s %d", _("Volume control"), level);
614 gtk_widget_set_tooltip_text(vol->plugin, tooltip);
615 g_free(tooltip);
616}
617
618/*
619 * Here we just update volume's vertical scale and mute check button.
620 * The rest will be updated by signal handelrs.
621 */
622static void volumealsa_update_display(VolumeALSAPlugin * vol)
623{
624 /* Mute. */
625 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), asound_is_muted(vol));
626 gtk_widget_set_sensitive(vol->mute_check, (asound_has_mute(vol)));
2ba86315
DB
627
628 /* Volume. */
2ba86315
DB
629 if (vol->volume_scale != NULL)
630 {
2ba86315 631 gtk_range_set_value(GTK_RANGE(vol->volume_scale), asound_get_volume(vol));
2ba86315 632 }
6cc5e1a6
DB
633}
634
7a1c5048 635struct mixer_desc
6cc5e1a6 636{
7a1c5048
AG
637 char * cmd;
638 char * exec;
639 gboolean needs_pa;
640 gboolean needs_term;
641};
2ba86315 642
7a1c5048
AG
643const struct mixer_desc mixers[] = {
644 /* those with needs_pa should be first! */
645 { "gnome-sound-applet", "gnome-sound-applet", TRUE, FALSE },
646 { "pavucontrol", "pavucontrol", TRUE, FALSE },
6d535cca
AG
647#ifdef DISABLE_ALSA
648 { "xfce4-mixer", "xfce4-mixer", FALSE, FALSE },
649 { "aumix", "aumix", FALSE, TRUE },
650#else
7a1c5048
AG
651 { "gnome-alsamixer", "gnome-alsamixer", FALSE, FALSE },
652 { "alsamixergui", "alsamixergui", FALSE, FALSE },
653 { "alsamixer", "alsamixer", FALSE, TRUE },
6d535cca 654#endif
7a1c5048
AG
655 { NULL }
656};
657
658static void volume_run_mixer(VolumeALSAPlugin * vol)
659{
660 char *path = NULL;
661 const gchar *command_line = NULL;
662 GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE;
663 int i;
664
665 /* check if command line was configured */
666 if (config_setting_lookup_string(vol->settings, "MixerCommand", &command_line))
667 if (config_setting_lookup_int(vol->settings, "MixerCommandTerm", &i) && i)
668 flags = G_APP_INFO_CREATE_NEEDS_TERMINAL;
669
670 /* if command isn't set in settings then let guess it */
671 if (command_line == NULL)
672 {
673 i = 0;
674 path = g_find_program_in_path("pulseaudio");
675 /* Assume that when pulseaudio is installed, it's launching every time */
676 if (path)
677 g_free(path);
678 /* Fallback to alsamixer when PA is not running, or when no PA utility is find */
679 else while (mixers[i].cmd && mixers[i].needs_pa)
680 i++;
681 for (; mixers[i].cmd; i++)
682 {
683 if ((path = g_find_program_in_path(mixers[i].exec)))
684 {
685 command_line = mixers[i].cmd;
686 if (mixers[i].needs_term)
687 flags = G_APP_INFO_CREATE_NEEDS_TERMINAL;
688 g_free(path);
689 break;
690 }
691 }
692 }
693
694 if (command_line)
2ba86315 695 {
7a1c5048
AG
696 fm_launch_command_simple(NULL, NULL, flags, command_line, NULL);
697 }
698 else
699 {
700 fm_show_error(NULL, NULL,
701 _("Error, you need to install an application to configure"
702 " the sound (pavucontrol, alsamixer ...)"));
703 }
704}
705
706static void _check_click(VolumeALSAPlugin * vol, int button, GdkModifierType mod)
707{
708 if (vol->slider_click == button && vol->slider_click_mods == mod)
709 {
710 /* Left-click. Show or hide the popup window. */
2ba86315
DB
711 if (vol->show_popup)
712 {
713 gtk_widget_hide(vol->popup_window);
714 vol->show_popup = FALSE;
715 }
716 else
717 {
2ba86315
DB
718 gtk_widget_show_all(vol->popup_window);
719 vol->show_popup = TRUE;
720 }
6cc5e1a6 721 }
7a1c5048 722 if (vol->mute_click == button && vol->mute_click_mods == mod)
2ba86315 723 {
7a1c5048 724 /* Middle-click. Toggle the mute status. */
2ba86315 725 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), ! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check)));
6cc5e1a6 726 }
7a1c5048
AG
727 if (vol->mixer_click == button && vol->mixer_click_mods == mod)
728 {
729 volume_run_mixer(vol);
730 }
731}
732
733/* Handler for "button-press-event" signal on main widget. */
734static gboolean volumealsa_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * panel)
735{
736 VolumeALSAPlugin * vol = lxpanel_plugin_get_data(widget);
737
738 if (event->button == 1)
739 {
740 _check_click(vol, 1,
741 event->state & gtk_accelerator_get_default_mod_mask());
742 }
743
744 return FALSE;
745}
746
747static gboolean volumealsa_button_release_event(GtkWidget * widget, GdkEventButton * event, VolumeALSAPlugin * vol)
748{
749 if (event->button != 1)
750 {
751 _check_click(vol, event->button,
752 event->state & gtk_accelerator_get_default_mod_mask());
753 }
754 return FALSE;
6cc5e1a6
DB
755}
756
2ba86315
DB
757/* Handler for "focus-out" signal on popup window. */
758static gboolean volumealsa_popup_focus_out(GtkWidget * widget, GdkEvent * event, VolumeALSAPlugin * vol)
6cc5e1a6 759{
2ba86315
DB
760 /* Hide the widget. */
761 gtk_widget_hide(vol->popup_window);
762 vol->show_popup = FALSE;
763 return FALSE;
6cc5e1a6
DB
764}
765
32a67dc7
DB
766/* Handler for "map" signal on popup window. */
767static void volumealsa_popup_map(GtkWidget * widget, VolumeALSAPlugin * vol)
768{
6b775dbb 769 lxpanel_plugin_adjust_popup_position(widget, vol->plugin);
32a67dc7
DB
770}
771
2ba86315
DB
772/* Handler for "value_changed" signal on popup window vertical scale. */
773static void volumealsa_popup_scale_changed(GtkRange * range, VolumeALSAPlugin * vol)
96dc8a45 774{
f7ecd6ce
AG
775 int level = gtk_range_get_value(GTK_RANGE(vol->volume_scale));
776 gboolean mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check));
2ba86315 777
f7ecd6ce
AG
778 /* Reflect the value of the control to the sound system. */
779 asound_set_volume(vol, level);
780
781 /*
782 * Redraw the controls.
783 * Scale and check button do not need to be updated, as these are always
784 * in sync with user's actions.
785 */
786 volumealsa_update_current_icon(vol, mute, level);
2ba86315
DB
787}
788
789/* Handler for "scroll-event" signal on popup window vertical scale. */
790static void volumealsa_popup_scale_scrolled(GtkScale * scale, GdkEventScroll * evt, VolumeALSAPlugin * vol)
791{
792 /* Get the state of the vertical scale. */
793 gdouble val = gtk_range_get_value(GTK_RANGE(vol->volume_scale));
794
795 /* Dispatch on scroll direction to update the value. */
796 if ((evt->direction == GDK_SCROLL_UP) || (evt->direction == GDK_SCROLL_LEFT))
96dc8a45 797 val += 2;
2ba86315 798 else
96dc8a45 799 val -= 2;
2ba86315
DB
800
801 /* Reset the state of the vertical scale. This provokes a "value_changed" event. */
802 gtk_range_set_value(GTK_RANGE(vol->volume_scale), CLAMP((int)val, 0, 100));
96dc8a45
DB
803}
804
2ba86315
DB
805/* Handler for "toggled" signal on popup window mute checkbox. */
806static void volumealsa_popup_mute_toggled(GtkWidget * widget, VolumeALSAPlugin * vol)
6cc5e1a6 807{
f7ecd6ce
AG
808 int level = gtk_range_get_value(GTK_RANGE(vol->volume_scale));
809 gboolean mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check));
2ba86315
DB
810
811 /* Reflect the mute toggle to the sound system. */
7a1c5048
AG
812#ifdef DISABLE_ALSA
813 if (mute)
814 {
815 vol->vol_before_mute = level;
816 asound_set_volume(vol, 0);
817 }
818 else
819 {
820 asound_set_volume(vol, vol->vol_before_mute);
821 }
822#else
4652f59b
DB
823 if (vol->master_element != NULL)
824 {
825 int chn;
826 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++)
f7ecd6ce 827 snd_mixer_selem_set_playback_switch(vol->master_element, chn, ((mute) ? 0 : 1));
4652f59b 828 }
7a1c5048 829#endif
6cc5e1a6 830
f7ecd6ce
AG
831 /*
832 * Redraw the controls.
833 * Scale and check button do not need to be updated, as these are always
834 * in sync with user's actions.
835 */
836 volumealsa_update_current_icon(vol, mute, level);
6cc5e1a6
DB
837}
838
7a1c5048
AG
839/* Hotkeys handlers */
840static void volume_up(const char *keystring, gpointer user_data)
841{
842 VolumeALSAPlugin * vol = (VolumeALSAPlugin *)user_data;
843 int val = (int)gtk_range_get_value(GTK_RANGE(vol->volume_scale)) + 2;
844 gtk_range_set_value(GTK_RANGE(vol->volume_scale), CLAMP(val, 0, 100));
845}
846
847static void volume_down(const char *keystring, gpointer user_data)
848{
849 VolumeALSAPlugin * vol = (VolumeALSAPlugin *)user_data;
850 int val = (int)gtk_range_get_value(GTK_RANGE(vol->volume_scale)) - 2;
851 gtk_range_set_value(GTK_RANGE(vol->volume_scale), CLAMP(val, 0, 100));
852}
853
854static void volume_mute(const char *keystring, gpointer user_data)
855{
856 VolumeALSAPlugin * vol = (VolumeALSAPlugin *)user_data;
857 gboolean muted = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol->mute_check));
858 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), !muted);
859}
860
2ba86315 861/* Build the window that appears when the top level widget is clicked. */
6b775dbb 862static void volumealsa_build_popup_window(GtkWidget *p)
6cc5e1a6 863{
6b775dbb 864 VolumeALSAPlugin * vol = lxpanel_plugin_get_data(p);
2ba86315
DB
865
866 /* Create a new window. */
7a1c5048 867 vol->popup_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2ba86315
DB
868 gtk_window_set_decorated(GTK_WINDOW(vol->popup_window), FALSE);
869 gtk_container_set_border_width(GTK_CONTAINER(vol->popup_window), 5);
870 gtk_window_set_default_size(GTK_WINDOW(vol->popup_window), 80, 140);
871 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->popup_window), TRUE);
872 gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->popup_window), TRUE);
7a1c5048 873 gtk_window_set_type_hint(GTK_WINDOW(vol->popup_window), GDK_WINDOW_TYPE_HINT_DIALOG);
2ba86315 874
32a67dc7 875 /* Connect signals. */
6b775dbb 876 g_signal_connect(G_OBJECT(vol->popup_window), "focus-out-event", G_CALLBACK(volumealsa_popup_focus_out), vol);
32a67dc7 877 g_signal_connect(G_OBJECT(vol->popup_window), "map", G_CALLBACK(volumealsa_popup_map), vol);
2ba86315
DB
878
879 /* Create a scrolled window as the child of the top level window. */
880 GtkWidget * scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
881 gtk_container_set_border_width (GTK_CONTAINER(scrolledwindow), 0);
882 gtk_widget_show(scrolledwindow);
883 gtk_container_add(GTK_CONTAINER(vol->popup_window), scrolledwindow);
6b775dbb 884 gtk_widget_set_can_focus(scrolledwindow, FALSE);
6cc5e1a6
DB
885 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
886 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_NONE);
887
2ba86315
DB
888 /* Create a viewport as the child of the scrolled window. */
889 GtkWidget * viewport = gtk_viewport_new(NULL, NULL);
890 gtk_container_add(GTK_CONTAINER(scrolledwindow), viewport);
891 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
6cc5e1a6
DB
892 gtk_widget_show(viewport);
893
2ba86315
DB
894 /* Create a frame as the child of the viewport. */
895 GtkWidget * frame = gtk_frame_new(_("Volume"));
6cc5e1a6
DB
896 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
897 gtk_container_add(GTK_CONTAINER(viewport), frame);
898
2ba86315
DB
899 /* Create a vertical box as the child of the frame. */
900 GtkWidget * box = gtk_vbox_new(FALSE, 0);
901 gtk_container_add(GTK_CONTAINER(frame), box);
6cc5e1a6 902
2ba86315
DB
903 /* Create a vertical scale as the child of the vertical box. */
904 vol->volume_scale = gtk_vscale_new(GTK_ADJUSTMENT(gtk_adjustment_new(100, 0, 100, 0, 0, 0)));
905 gtk_scale_set_draw_value(GTK_SCALE(vol->volume_scale), FALSE);
906 gtk_range_set_inverted(GTK_RANGE(vol->volume_scale), TRUE);
907 gtk_box_pack_start(GTK_BOX(box), vol->volume_scale, TRUE, TRUE, 0);
6cc5e1a6 908
2ba86315 909 /* Value-changed and scroll-event signals. */
6b775dbb 910 vol->volume_scale_handler = g_signal_connect(vol->volume_scale, "value-changed", G_CALLBACK(volumealsa_popup_scale_changed), vol);
2ba86315 911 g_signal_connect(vol->volume_scale, "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol);
6cc5e1a6 912
2ba86315 913 /* Create a check button as the child of the vertical box. */
96dc8a45 914 vol->mute_check = gtk_check_button_new_with_label(_("Mute"));
96dc8a45 915 gtk_box_pack_end(GTK_BOX(box), vol->mute_check, FALSE, FALSE, 0);
2ba86315 916 vol->mute_check_handler = g_signal_connect(vol->mute_check, "toggled", G_CALLBACK(volumealsa_popup_mute_toggled), vol);
6cc5e1a6
DB
917}
918
2ba86315 919/* Plugin constructor. */
6b775dbb 920static GtkWidget *volumealsa_constructor(LXPanel *panel, config_setting_t *settings)
6cc5e1a6 921{
2ba86315
DB
922 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
923 VolumeALSAPlugin * vol = g_new0(VolumeALSAPlugin, 1);
6b775dbb 924 GtkWidget *p;
7a1c5048 925 const char *tmp_str;
6cc5e1a6 926
7a1c5048 927#ifndef DISABLE_ALSA
f7ecd6ce
AG
928 /* Read config necessary for proper initialization of ALSA. */
929 config_setting_lookup_int(settings, "UseAlsamixerVolumeMapping", &vol->alsamixer_mapping);
7a1c5048
AG
930 if (config_setting_lookup_string(settings, "MasterChannel", &tmp_str))
931 vol->master_channel = g_strdup(tmp_str);
932 if (!config_setting_lookup_int(settings, "CardNumber", &vol->used_device))
933 vol->used_device = -1;
934#else
935 vol->master_channel = SOUND_MIXER_VOLUME;
936 if (config_setting_lookup_string(settings, "MasterChannel", &tmp_str))
937 {
938 if (strcmp(tmp_str, "PCM") == 0)
939 vol->master_channel = SOUND_MIXER_PCM;
940 else if (strcmp(tmp_str, "Headphone") == 0)
941 vol->master_channel = SOUND_MIXER_PHONEOUT;
942 }
943#endif
944 if (config_setting_lookup_string(settings, "MuteButton", &tmp_str))
945 vol->mute_click = panel_config_click_parse(tmp_str, &vol->mute_click_mods);
946 else
947 vol->mute_click = 2; /* middle-click default */
948 if (config_setting_lookup_string(settings, "SliderButton", &tmp_str))
949 vol->slider_click = panel_config_click_parse(tmp_str, &vol->slider_click_mods);
950 else
951 vol->slider_click = 1; /* left-click default */
952 if (config_setting_lookup_string(settings, "MixerButton", &tmp_str))
953 vol->mixer_click = panel_config_click_parse(tmp_str, &vol->mixer_click_mods);
954 if (config_setting_lookup_string(settings, "VolumeUpKey", &tmp_str))
955 lxpanel_apply_hotkey(&vol->hotkey_up, tmp_str, volume_up, vol, FALSE);
956 if (config_setting_lookup_string(settings, "VolumeDownKey", &tmp_str))
957 lxpanel_apply_hotkey(&vol->hotkey_down, tmp_str, volume_down, vol, FALSE);
958 if (config_setting_lookup_string(settings, "VolumeMuteKey", &tmp_str))
959 lxpanel_apply_hotkey(&vol->hotkey_mute, tmp_str, volume_mute, vol, FALSE);
f7ecd6ce 960
2ba86315
DB
961 /* Initialize ALSA. If that fails, present nothing. */
962 if ( ! asound_initialize(vol))
6b775dbb
AG
963 {
964 volumealsa_destructor(vol);
965 return NULL;
966 }
6cc5e1a6 967
2ba86315 968 /* Allocate top level widget and set into Plugin widget pointer. */
6b775dbb
AG
969 vol->panel = panel;
970 vol->plugin = p = gtk_event_box_new();
19ab5cea 971 vol->settings = settings;
6b775dbb 972 lxpanel_plugin_set_data(p, vol, volumealsa_destructor);
6b775dbb 973 gtk_widget_set_tooltip_text(p, _("Volume control"));
6cc5e1a6 974
2ba86315 975 /* Allocate icon as a child of top level. */
f7ecd6ce
AG
976 vol->tray_icon = lxpanel_image_new_for_icon(panel, "audio-volume-muted-panel",
977 -1, ICONS_MUTE);
6b775dbb 978 gtk_container_add(GTK_CONTAINER(p), vol->tray_icon);
6cc5e1a6 979
2ba86315
DB
980 /* Initialize window to appear when icon clicked. */
981 volumealsa_build_popup_window(p);
6cc5e1a6 982
2ba86315 983 /* Connect signals. */
6b775dbb 984 g_signal_connect(G_OBJECT(p), "scroll-event", G_CALLBACK(volumealsa_popup_scale_scrolled), vol );
7a1c5048 985 g_signal_connect(G_OBJECT(p), "button-release-event", G_CALLBACK(volumealsa_button_release_event), vol );
6cc5e1a6 986
2ba86315
DB
987 /* Update the display, show the widget, and return. */
988 volumealsa_update_display(vol);
8713e384 989 volumealsa_update_current_icon(vol, asound_is_muted(vol), asound_get_volume(vol));
6b775dbb
AG
990 gtk_widget_show_all(p);
991 return p;
2ba86315 992}
6cc5e1a6 993
2ba86315 994/* Plugin destructor. */
6b775dbb 995static void volumealsa_destructor(gpointer user_data)
2ba86315 996{
6b775dbb 997 VolumeALSAPlugin * vol = (VolumeALSAPlugin *) user_data;
6cc5e1a6 998
7a1c5048
AG
999 lxpanel_apply_hotkey(&vol->hotkey_up, NULL, NULL, NULL, FALSE);
1000 lxpanel_apply_hotkey(&vol->hotkey_down, NULL, NULL, NULL, FALSE);
1001 lxpanel_apply_hotkey(&vol->hotkey_mute, NULL, NULL, NULL, FALSE);
1002
eea54180 1003 asound_deinitialize(vol);
6cc5e1a6 1004
2ba86315
DB
1005 /* If the dialog box is open, dismiss it. */
1006 if (vol->popup_window != NULL)
1007 gtk_widget_destroy(vol->popup_window);
6cc5e1a6 1008
7a1c5048 1009#ifndef DISABLE_ALSA
6b775dbb
AG
1010 if (vol->restart_idle)
1011 g_source_remove(vol->restart_idle);
1012
7a1c5048
AG
1013 g_free(vol->master_channel);
1014#endif
1015
2ba86315
DB
1016 /* Deallocate all memory. */
1017 g_free(vol);
6cc5e1a6
DB
1018}
1019
7a1c5048
AG
1020#ifndef DISABLE_ALSA
1021static GtkListStore *alsa_make_channels_list(VolumeALSAPlugin *vol, int *active)
514580cf 1022{
7a1c5048
AG
1023 GtkListStore *list;
1024 GtkTreeIter iter;
1025 snd_mixer_selem_id_t *sid;
1026 snd_mixer_elem_t *elem;
1027 const char *name;
1028 int i;
19ab5cea 1029
7a1c5048
AG
1030 snd_mixer_selem_id_alloca(&sid);
1031 list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); /* desc, value */
1032 for (elem = snd_mixer_first_elem(vol->mixer), i = 0; elem != NULL;
1033 elem = snd_mixer_elem_next(elem), i++)
514580cf 1034 {
7a1c5048
AG
1035 if (snd_mixer_selem_is_active(elem) &&
1036 snd_mixer_selem_has_playback_volume(elem) &&
1037 !snd_mixer_selem_has_capture_volume(elem) &&
1038 !snd_mixer_selem_has_capture_switch(elem))
514580cf 1039 {
7a1c5048
AG
1040 snd_mixer_selem_get_id(elem, sid);
1041 name = snd_mixer_selem_id_get_name(sid);
1042 gtk_list_store_insert_with_values(list, &iter, i, 0, _(name),
1043 1, name, -1);
1044 if (elem == vol->master_element)
1045 *active = i;
514580cf 1046 }
7a1c5048
AG
1047 }
1048 return list;
1049}
1050
1051static void card_selector_changed(GtkComboBox *card_selector, VolumeALSAPlugin *vol)
1052{
1053 GtkTreeModel *model = gtk_combo_box_get_model(card_selector);
1054 GtkTreeIter iter;
1055 int old_card = vol->used_device;
1056 int i = gtk_combo_box_get_active(card_selector);
1057 char *old_channel;
1058
1059 gtk_tree_model_iter_nth_child(model, &iter, NULL, i);
1060 gtk_tree_model_get(model, &iter, 1, &vol->used_device, -1);
1061 asound_deinitialize(vol);
1062 if (!asound_initialize(vol))
1063 {
1064 /* could not change card with the same master channel, try default */
1065 old_channel = vol->master_channel;
1066 vol->master_channel = NULL;
1067 asound_deinitialize(vol);
1068 if (!asound_initialize(vol))
514580cf 1069 {
7a1c5048
AG
1070 g_warning("could not set card to %d", vol->used_device);
1071 vol->master_channel = old_channel;
1072 vol->used_device = old_card;
1073 //FIXME: reset the selector back
1074 /* schedule to restart with old settings */
1075 if (vol->restart_idle == 0)
1076 vol->restart_idle = g_timeout_add_seconds(1, asound_restart, vol);
1077 return;
514580cf 1078 }
7a1c5048
AG
1079 g_free(old_channel);
1080 config_group_set_string(vol->settings, "MasterChannel", NULL);
514580cf 1081 }
7a1c5048
AG
1082 /* remember and apply selection */
1083 volumealsa_update_display(vol);
1084 config_group_set_int(vol->settings, "CardNumber", vol->used_device);
1085 /* rebuild channel selection list */
1086 i = -1;
1087 model = GTK_TREE_MODEL(alsa_make_channels_list(vol, &i));
1088 gtk_combo_box_set_model(GTK_COMBO_BOX(vol->channel_selector), model);
1089 gtk_combo_box_set_active(GTK_COMBO_BOX(vol->channel_selector), i);
1090 g_object_unref(model);
1091}
1092#endif
514580cf 1093
7a1c5048
AG
1094static void channel_selector_changed(GtkComboBox *channel_selector, VolumeALSAPlugin *vol)
1095{
1096 GtkTreeModel *model = gtk_combo_box_get_model(channel_selector);
1097 GtkTreeIter iter;
1098#ifdef DISABLE_ALSA
1099 int ch; /* channel index */
1100#else
1101 char *ch; /* channel name */
1102#endif
1103 int i = gtk_combo_box_get_active(channel_selector);
1104
1105 gtk_tree_model_iter_nth_child(model, &iter, NULL, i);
1106 gtk_tree_model_get(model, &iter, 1, &ch, -1);
1107#ifdef DISABLE_ALSA
1108 config_group_set_int(vol->settings, "MasterChannel", ch);
1109#else
1110 config_group_set_string(vol->settings, "MasterChannel", ch);
1111 asound_find_element(vol, (const char **)&ch, 1); //FIXME: is error possible?
1112 /* Set the playback volume range as we wish it. */
1113 if (!vol->alsamixer_mapping)
1114 snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);
1115 /* g_debug("MasterChannel changed: %s", ch); */
1116 g_free(vol->master_channel);
1117#endif
1118 vol->master_channel = ch; /* just take it instead of alloc + free */
1119 volumealsa_update_display(vol);
1120}
1121
1122static void mixer_selector_changed(GtkComboBox *mixer_selector, VolumeALSAPlugin *vol)
1123{
1124 GtkWidget *mixer_entry = gtk_bin_get_child(GTK_BIN(mixer_selector));
1125 const char *cmd, *set;
1126 GtkTreeModel *model;
1127 GtkTreeIter iter;
1128 int i;
1129
1130 i = gtk_combo_box_get_active(mixer_selector);
1131 if (i < 0)
1132 /* it was just editing */
1133 return;
1134 if (!config_setting_lookup_string(vol->settings, "MixerCommand", &set))
1135 set = NULL;
1136 cmd = gtk_entry_get_text((GtkEntry *)mixer_entry);
1137 if (set)
1138 {
1139 if (strcmp(set, cmd) == 0)
1140 /* not changed */
1141 return;
1142 }
1143 else if (gtk_combo_box_get_active(mixer_selector) == 0)
1144 /* it's left at default */
1145 return;
1146 model = gtk_combo_box_get_model(mixer_selector);
1147 gtk_tree_model_iter_nth_child(model, &iter, NULL, i);
1148 gtk_tree_model_get(model, &iter, 1, &i, -1);
1149 /* g_debug("new choice: %s needs_term=%d", cmd, i); */
1150 config_group_set_string(vol->settings, "MixerCommand", cmd);
1151 config_group_set_int(vol->settings, "MixerCommandTerm", i);
1152}
1153
1154struct mixer_selector_check_data
1155{
1156 GtkComboBox *mixer_selector;
1157 const char *text;
1158 int needs_term;
1159};
1160
1161static gboolean mixer_selector_check(GtkTreeModel *model, GtkTreePath *path,
1162 GtkTreeIter *iter, gpointer user_data)
1163{
1164 struct mixer_selector_check_data *data = user_data;
1165 char *cmd;
1166
1167 gtk_tree_model_get(model, iter, 0, &cmd, 1, &data->needs_term, -1);
1168 if (cmd && strcmp(cmd, data->text) == 0)
514580cf 1169 {
7a1c5048
AG
1170 int *indices = gtk_tree_path_get_indices(path);
1171 gtk_combo_box_set_active(data->mixer_selector, indices[0]);
1172 g_free(cmd);
1173 return TRUE;
1174 }
1175 g_free(cmd);
1176 return FALSE;
1177}
1178
1179static gboolean mixer_selector_focus_out(GtkWidget *mixer_entry,
1180 GdkEvent *evt, VolumeALSAPlugin *vol)
1181{
1182 struct mixer_selector_check_data data;
1183 GtkTreeModel *model;
1184
1185 data.mixer_selector = GTK_COMBO_BOX(gtk_widget_get_parent(mixer_entry));
1186 data.text = gtk_entry_get_text((GtkEntry *)mixer_entry);
1187 data.needs_term = 0;
1188 model = gtk_combo_box_get_model(data.mixer_selector);
1189
1190 /* check if current value is one of model choices */
1191 if (gtk_combo_box_get_active(data.mixer_selector) < 0)
1192 gtk_tree_model_foreach(model, &mixer_selector_check, &data);
1193 /* check executable and remember selection */
1194 if (gtk_combo_box_get_active(data.mixer_selector) < 0)
1195 {
1196 /* check only user input since predefined choices were tested already */
1197 char *exec, *path;
1198
1199 /* g_debug("user entered mixer: %s", data.text); */
1200 exec = strchr(data.text, ' ');
1201 if (exec)
1202 exec = g_strndup(data.text, exec - data.text);
1203 path = g_find_program_in_path(exec ? exec : data.text);
1204 g_free(exec);
1205 g_free(path);
1206 if (path == NULL)
514580cf 1207 {
7a1c5048
AG
1208 /* invalid executable requested, ignore it then */
1209 g_warning("%s cannot be executed, ignoring it", data.text);
1210 return FALSE;
514580cf 1211 }
7a1c5048
AG
1212 }
1213 config_group_set_string(vol->settings, "MixerCommand", data.text);
1214 config_group_set_int(vol->settings, "MixerCommandTerm", data.needs_term);
1215 return FALSE;
1216}
1217
1218static gboolean mixer_selector_key_press(GtkWidget *mixer_entry,
1219 GdkEventKey *evt, VolumeALSAPlugin *vol)
1220{
1221 if (evt->keyval == GDK_KEY_Return)
1222 /* loose focus on Enter press */
1223 gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(mixer_entry)), NULL);
1224 return FALSE;
1225}
1226
1227static gboolean mute_button_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1228{
1229 int n;
1230 GdkModifierType mods;
1231
1232 n = panel_config_click_parse(click, &mods);
1233 if (n == 0 || ((n != vol->mixer_click || mods != vol->mixer_click_mods) &&
1234 (n != vol->slider_click || mods != vol->slider_click_mods)))
1235 {
1236 config_group_set_string(vol->settings, "MuteButton", click);
1237 vol->mute_click = n;
1238 vol->mute_click_mods = mods;
1239 return TRUE;
1240 }
1241 //FIXME: show a message?
1242 return FALSE;
1243}
1244
1245static gboolean mixer_button_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1246{
1247 int n;
1248 GdkModifierType mods;
1249
1250 n = panel_config_click_parse(click, &mods);
1251 if (n == 0 || ((n != vol->mute_click || mods != vol->mute_click_mods) &&
1252 (n != vol->slider_click || mods != vol->slider_click_mods)))
1253 {
1254 config_group_set_string(vol->settings, "MixerButton", click);
1255 vol->mixer_click = n;
1256 vol->mixer_click_mods = mods;
1257 return TRUE;
1258 }
1259 //FIXME: show a message?
1260 return FALSE;
1261}
1262
1263static gboolean volume_button_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1264{
1265 int n;
1266 GdkModifierType mods;
1267
1268 n = panel_config_click_parse(click, &mods);
1269 if (n == 0 || ((n != vol->mixer_click || mods != vol->mixer_click_mods) &&
1270 (n != vol->mute_click || mods != vol->mute_click_mods)))
1271 {
1272 config_group_set_string(vol->settings, "SliderButton", click);
1273 vol->slider_click = n;
1274 vol->slider_click_mods = mods;
1275 return TRUE;
1276 }
1277 //FIXME: show a message?
1278 return FALSE;
1279}
1280
1281static gboolean up_key_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1282{
1283 gboolean res;
1284
1285 res = lxpanel_apply_hotkey(&vol->hotkey_up, click, &volume_up, vol, TRUE);
1286 if (res)
1287 config_group_set_string(vol->settings, "VolumeUpKey", click);
1288 return res;
1289}
1290
1291static gboolean down_key_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1292{
1293 gboolean res;
1294
1295 res = lxpanel_apply_hotkey(&vol->hotkey_down, click, &volume_down, vol, TRUE);
1296 if (res)
1297 config_group_set_string(vol->settings, "VolumeDownKey", click);
1298 return res;
1299}
1300
1301static gboolean mute_key_changed(GtkWidget *btn, char *click, VolumeALSAPlugin *vol)
1302{
1303 gboolean res;
1304
1305 res = lxpanel_apply_hotkey(&vol->hotkey_mute, click, &volume_mute, vol, TRUE);
1306 if (res)
1307 config_group_set_string(vol->settings, "VolumeMuteKey", click);
1308 return res;
1309}
1310
1311#if THING_THAT_NEVER_HAPPEN
1312/* Just to have these translated */
1313N_("Line"), N_("LineOut"), N_("Front"), N_("Surround"), N_("Center"), N_("Speaker+LO");
1314#endif
1315
1316/* Callback when the configuration dialog is to be shown. */
1317static GtkWidget *volumealsa_configure(LXPanel *panel, GtkWidget *p)
1318{
1319 VolumeALSAPlugin *vol = lxpanel_plugin_get_data(p);
1320 const char *tmp_str;
1321 char *path;
1322 GtkListStore *list;
1323 GtkCellRenderer *column;
1324#ifndef DISABLE_ALSA
1325 snd_mixer_selem_id_t *sid;
1326 snd_mixer_elem_t *elem;
1327 snd_hctl_t *hctl;
1328 GtkWidget *card_selector;
1329#endif
1330 GtkWidget *mute_button;
1331 GtkWidget *volume_button;
1332 GtkWidget *mixer_button;
1333 GtkWidget *up_key;
1334 GtkWidget *down_key;
1335 GtkWidget *mute_key;
1336 GtkWidget *mixer_selector;
1337 GtkWidget *mixer_entry;
1338 GtkTreeIter iter;
1339 int active = 0;
1340 int i = 0;
7a1c5048
AG
1341 int j = -1;
1342
6d535cca 1343#ifndef DISABLE_ALSA
7a1c5048
AG
1344 snd_mixer_selem_id_alloca(&sid);
1345 /* setup card selector */
1346 list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); /* desc, num */
1347 if (snd_hctl_open(&hctl, "default", 0) == 0)
1348 {
1349 /* check if "default" isn't a HW, so include it in the list */
1350 if (snd_ctl_type(snd_hctl_ctl(hctl)) != 0)
19ab5cea 1351 {
7a1c5048
AG
1352 gtk_list_store_insert_with_values(list, &iter, i++, 0, _("default"),
1353 1, j, -1);
1354 if (vol->used_device < 0)
1355 active = 0;
19ab5cea 1356 }
7a1c5048
AG
1357 snd_hctl_close(hctl);
1358 }
1359 while (snd_card_next(&j) == 0 && j >= 0)
1360 {
1361 char *name = NULL;
1362 snd_mixer_t *mixer;
1363 char id[16];
1364
1365 if (snd_card_get_name(j, &name) == 0)
514580cf 1366 {
7a1c5048
AG
1367 /* test if there any available channel */
1368 snprintf(id, sizeof(id), "hw:%d", j);
1369 snd_mixer_open(&mixer, 0);
1370 snd_mixer_attach(mixer, id);
1371 snd_mixer_selem_register(mixer, NULL, NULL);
1372 snd_mixer_load(mixer);
1373 for (elem = snd_mixer_first_elem(mixer); elem != NULL;
1374 elem = snd_mixer_elem_next(elem))
1375 {
1376 if (snd_mixer_selem_is_active(elem) &&
1377 snd_mixer_selem_has_playback_volume(elem) &&
1378 !snd_mixer_selem_has_capture_volume(elem) &&
1379 !snd_mixer_selem_has_capture_switch(elem))
1380 break;
1381 }
1382 snd_mixer_close(mixer);
1383 if (elem != NULL)
1384 {
1385 g_debug("found soundcard: %s", name);
1386 gtk_list_store_insert_with_values(list, &iter, i++, 0, name,
1387 1, j, -1);
1388 if (vol->used_device == j)
1389 active = i;
1390 }
1391 else
1392 g_debug("no elements in soundcard %s", name);
1393 free(name);
514580cf
DB
1394 }
1395 }
7a1c5048
AG
1396 card_selector = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
1397 g_object_unref(list);
1398 /* gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(card_selector), 1); */
1399 column = gtk_cell_renderer_text_new();
1400 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(card_selector), column, TRUE);
1401 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(card_selector), column,
1402 "text", 0, NULL);
1403 gtk_combo_box_set_active(GTK_COMBO_BOX(card_selector), active);
1404 g_signal_connect(card_selector, "changed",
1405 G_CALLBACK(card_selector_changed), vol);
1406 g_signal_connect(card_selector, "scroll-event", G_CALLBACK(gtk_true), NULL);
1407#endif
1408
1409 /* setup channel selector */
1410#ifdef DISABLE_ALSA
1411 list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); /* desc, index */
1412 gtk_list_store_insert_with_values(list, &iter, 0, 0, _("Master"),
1413 1, SOUND_MIXER_VOLUME, -1);
1414 if (vol->master_channel == SOUND_MIXER_VOLUME)
1415 active = 0;
1416 gtk_list_store_insert_with_values(list, &iter, 1, 0, _("PCM"),
1417 1, SOUND_MIXER_PCM, -1);
1418 if (vol->master_channel == SOUND_MIXER_VOLUME)
1419 active = 1;
1420 gtk_list_store_insert_with_values(list, &iter, 2, 0, _("Headphone"),
1421 1, SOUND_MIXER_PHONEOUT, -1);
1422 if (vol->master_channel == SOUND_MIXER_VOLUME)
1423 active = 2;
1424#else
1425 list = alsa_make_channels_list(vol, &active);
1426#endif
1427 vol->channel_selector = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
1428 g_object_unref(list);
1429 /* gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(vol->channel_selector), 1); */
1430 column = gtk_cell_renderer_text_new();
1431 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(vol->channel_selector), column, TRUE);
1432 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(vol->channel_selector), column,
1433 "text", 0, NULL);
1434 gtk_combo_box_set_active(GTK_COMBO_BOX(vol->channel_selector), active);
1435 g_signal_connect(vol->channel_selector, "changed",
1436 G_CALLBACK(channel_selector_changed), vol);
1437 g_signal_connect(vol->channel_selector, "scroll-event", G_CALLBACK(gtk_true), NULL);
1438
1439 /* setup buttons */
1440 if (!config_setting_lookup_string(vol->settings, "SliderButton", &tmp_str))
1441 tmp_str = "1";
1442 volume_button = panel_config_click_button_new(_("Click for Volume Slider"), tmp_str);
1443 g_signal_connect(volume_button, "changed", G_CALLBACK(volume_button_changed), vol);
1444 if (!config_setting_lookup_string(vol->settings, "MuteButton", &tmp_str))
1445 tmp_str = "2";
1446 mute_button = panel_config_click_button_new(_("Click for Toggle Mute"), tmp_str);
1447 g_signal_connect(mute_button, "changed", G_CALLBACK(mute_button_changed), vol);
1448 if (!config_setting_lookup_string(vol->settings, "MixerButton", &tmp_str))
1449 tmp_str = NULL;
1450 mixer_button = panel_config_click_button_new(_("Click for Open Mixer"), tmp_str);
1451 g_signal_connect(mixer_button, "changed", G_CALLBACK(mixer_button_changed), vol);
1452
1453 /* setup hotkeys */
1454 up_key = panel_config_hotkey_button_new(_("Hotkey for Volume Up"), vol->hotkey_up);
1455 g_signal_connect(up_key, "changed", G_CALLBACK(up_key_changed), vol);
1456 down_key = panel_config_hotkey_button_new(_("Hotkey for Volume Down"), vol->hotkey_down);
1457 g_signal_connect(down_key, "changed", G_CALLBACK(down_key_changed), vol);
1458 mute_key = panel_config_hotkey_button_new(_("Hotkey for Volume Mute"), vol->hotkey_mute);
1459 g_signal_connect(mute_key, "changed", G_CALLBACK(mute_key_changed), vol);
1460
1461 /* setup mixer selector */
1462 if (!config_setting_lookup_string(vol->settings, "MixerCommand", &tmp_str))
1463 tmp_str = NULL;
1464 active = -1;
1465 i = j = 0;
1466 list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); /* line, needs_term */
1467 path = g_find_program_in_path("pulseaudio");
1468 if (path)
1469 g_free(path);
1470 else while (mixers[i].cmd && mixers[i].needs_pa)
1471 i++;
1472 for (; mixers[i].cmd; i++)
514580cf 1473 {
7a1c5048
AG
1474 path = g_find_program_in_path(mixers[i].exec);
1475 if (path)
1476 {
1477 if (tmp_str && active < 0 && strcmp(tmp_str, mixers[i].cmd) == 0)
1478 {
1479 active = j;
1480 tmp_str = NULL;
1481 }
1482 gtk_list_store_insert_with_values(list, &iter, j++, 0, mixers[i].cmd,
1483 1, (int)mixers[i].needs_term,
1484 -1);
1485 g_free(path);
1486 }
514580cf 1487 }
7a1c5048 1488 if (tmp_str)
514580cf 1489 {
7a1c5048
AG
1490 active = j;
1491 /* FIXME: support "needs terminal" for custom MixerCommand */
1492 gtk_list_store_insert_with_values(list, &iter, j, 0, tmp_str, 1, 0, -1);
514580cf 1493 }
7a1c5048
AG
1494 if (active < 0)
1495 active = 0;
1496#if GTK_CHECK_VERSION(2, 24, 0)
1497 mixer_selector = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(list));
1498 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(mixer_selector), 0);
1499#else
1500 mixer_selector = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(list), 0);
1501#endif
1502 g_object_unref(list);
1503 /* gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(mixer_selector), 1); */
1504 gtk_combo_box_set_active(GTK_COMBO_BOX(mixer_selector), active);
1505 mixer_entry = gtk_bin_get_child(GTK_BIN(mixer_selector));
1506 g_signal_connect(mixer_entry, "key-press-event",
1507 G_CALLBACK(mixer_selector_key_press), vol);
1508 g_signal_connect(mixer_selector, "changed",
1509 G_CALLBACK(mixer_selector_changed), vol);
1510 g_signal_connect(mixer_entry, "focus-out-event",
1511 G_CALLBACK(mixer_selector_focus_out), vol);
1512 g_signal_connect(mixer_selector, "scroll-event", G_CALLBACK(gtk_true), NULL);
1513
1514 return lxpanel_generic_config_dlg(_("Volume Control"), panel, NULL, p,
1515#ifndef DISABLE_ALSA
1516 _("Audio Card"), NULL, CONF_TYPE_TRIM,
1517 "", card_selector, CONF_TYPE_EXTERNAL,
1518#endif
1519 _("Channel to Operate"), NULL, CONF_TYPE_TRIM,
1520 "", vol->channel_selector, CONF_TYPE_EXTERNAL,
1521 "", volume_button, CONF_TYPE_EXTERNAL,
1522 "", mute_button, CONF_TYPE_EXTERNAL,
1523 "", mixer_button, CONF_TYPE_EXTERNAL,
1524 "", up_key, CONF_TYPE_EXTERNAL,
1525 "", down_key, CONF_TYPE_EXTERNAL,
1526 "", mute_key, CONF_TYPE_EXTERNAL,
1527 _("Command to Open Mixer"), NULL, CONF_TYPE_TRIM,
1528 "", mixer_selector, CONF_TYPE_EXTERNAL,
1529 NULL);
514580cf
DB
1530}
1531
2ba86315 1532/* Callback when panel configuration changes. */
6b775dbb 1533static void volumealsa_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
2ba86315
DB
1534{
1535 /* Do a full redraw. */
6b775dbb 1536 volumealsa_update_display(lxpanel_plugin_get_data(p));
2ba86315 1537}
6cc5e1a6 1538
7a1c5048
AG
1539static gboolean volumealsa_update_context_menu(GtkWidget *plugin, GtkMenu *menu)
1540{
1541 GtkWidget *img = gtk_image_new_from_stock("gtk-directory", GTK_ICON_SIZE_MENU);
1542 GtkWidget *menu_item = gtk_image_menu_item_new_with_label(_("Launch Mixer"));
1543 //FIXME: precheck and disable if MixerCommand not set
1544 gtk_image_menu_item_set_image((GtkImageMenuItem *)menu_item, img);
1545 g_signal_connect_swapped(menu_item, "activate", G_CALLBACK(volume_run_mixer),
1546 lxpanel_plugin_get_data(plugin));
1547 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1548 return FALSE;
1549}
1550
1551#ifndef DISABLE_ALSA
1552static LXPanelPluginInit _volumealsa_init = {
1553 .name = N_("Volume Control"),
1554 .description = N_("Display and control volume"),
1555
1556 .superseded = TRUE,
1557 .new_instance = volumealsa_constructor,
1558 .config = volumealsa_configure,
1559 .reconfigure = volumealsa_panel_configuration_changed,
1560 .update_context_menu = volumealsa_update_context_menu,
1561 .button_press_event = volumealsa_button_press_event
1562};
1563
1564static void volumealsa_init(void)
1565{
1566 lxpanel_register_plugin_type("volumealsa", &_volumealsa_init);
1567}
1568#endif
1569
1570FM_DEFINE_MODULE(lxpanel_gtk, volume)
2ba86315 1571
6b775dbb
AG
1572/* Plugin descriptor. */
1573LXPanelPluginInit fm_module_init_lxpanel_gtk = {
1574 .name = N_("Volume Control"),
7a1c5048
AG
1575 .description = N_("Display and control volume"),
1576#ifndef DISABLE_ALSA
1577 .init = volumealsa_init,
1578#endif
6b775dbb
AG
1579
1580 .new_instance = volumealsa_constructor,
1581 .config = volumealsa_configure,
1582 .reconfigure = volumealsa_panel_configuration_changed,
7a1c5048 1583 .update_context_menu = volumealsa_update_context_menu,
6b775dbb 1584 .button_press_event = volumealsa_button_press_event
6cc5e1a6 1585};
eea54180
DB
1586
1587/* vim: set sw=4 et sts=4 : */