2
* This file is a part of the Cairo-Dock project
4
* Copyright : (C) see the 'copyright' file.
5
* E-mail : see the 'copyright' file.
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU General Public License
9
* as published by the Free Software Foundation; either version 3
10
* of the License, or (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
#include <glib/gi18n.h>
25
#include "applet-struct.h"
26
#include "applet-draw.h"
27
#include "applet-generic.h"
28
#include "applet-backend-alsamixer.h"
30
static int mixer_level = 0;
31
static struct snd_mixer_selem_regopt mixer_options;
32
static gboolean mixer_is_mute (void);
33
static int mixer_get_mean_volume (void);
35
static gchar *_mixer_get_card_id_from_name (const gchar *cName)
38
return g_strdup ("default");
42
while (snd_card_next (&iCardID) == 0 && iCardID != -1)
44
snd_card_get_name (iCardID, &cName2);
45
cd_debug ("+ card %d: %s", iCardID, cName2);
48
if (strcmp (cName2, cName) == 0)
51
return g_strdup_printf ("hw:%d", iCardID);
55
return g_strdup ("default");
58
static void mixer_init (const gchar *cCardName) // this function is taken from AlsaMixer.
60
snd_ctl_card_info_t *hw_info = NULL; // ne pas liberer.
61
snd_ctl_t *ctl_handle = NULL;
63
snd_ctl_card_info_alloca (&hw_info);
66
gchar *cCardID = _mixer_get_card_id_from_name (cCardName);
69
if ((err = snd_ctl_open (&ctl_handle, cCardID, 0)) < 0)
71
myData.cErrorMessage = g_strdup_printf (D_("I couldn't open card '%s'"), cCardID);
75
if ((err = snd_ctl_card_info (ctl_handle, hw_info)) < 0)
77
myData.cErrorMessage = g_strdup_printf (D_("Card '%s' opened but I couldn't get any info"), cCardID);
81
snd_ctl_close (ctl_handle);
84
if ((err = snd_mixer_open (&myData.mixer_handle, 0)) < 0)
86
myData.cErrorMessage = g_strdup (D_("I couldn't open the mixer"));
90
if (mixer_level == 0 && (err = snd_mixer_attach (myData.mixer_handle, cCardID)) < 0)
92
snd_mixer_free (myData.mixer_handle);
93
myData.mixer_handle = NULL;
95
myData.cErrorMessage = g_strdup (D_("I couldn't attach the mixer to the card"));
98
if ((err = snd_mixer_selem_register (myData.mixer_handle, mixer_level > 0 ? &mixer_options : NULL, NULL)) < 0)
100
snd_mixer_free (myData.mixer_handle);
101
myData.mixer_handle = NULL;
103
myData.cErrorMessage = g_strdup (D_("I couldn't register options"));
106
if ((err = snd_mixer_load (myData.mixer_handle)) < 0)
108
snd_mixer_free (myData.mixer_handle);
109
myData.mixer_handle = NULL;
111
myData.cErrorMessage = g_strdup (D_("I couldn't load the mixer"));
115
myData.mixer_card_name = g_strdup (snd_ctl_card_info_get_name(hw_info));
116
myData.mixer_device_name = g_strdup (snd_ctl_card_info_get_mixername(hw_info));
117
cd_debug ("myData.mixer_card_name : %s ; myData.mixer_device_name : %s", myData.mixer_card_name, myData.mixer_device_name);
121
void mixer_stop (void)
123
if (myData.mixer_handle != NULL)
125
gchar *cCardID = _mixer_get_card_id_from_name (myConfig.card_id);
126
snd_mixer_detach (myData.mixer_handle, cCardID);
128
snd_mixer_close (myData.mixer_handle);
129
myData.mixer_handle = NULL;
130
myData.pControledElement = NULL;
131
myData.pControledElement2 = NULL;
133
g_free (myData.cErrorMessage);
134
myData.cErrorMessage = NULL;
135
g_free (myData.mixer_card_name);
136
myData.mixer_card_name = NULL;
137
g_free (myData.mixer_device_name);
138
myData.mixer_device_name= NULL;
143
GList *mixer_get_cards_list (void)
149
pList = g_list_append (pList, (gpointer) g_strdup(""));
150
for (iCardID = 0; snd_card_get_name (iCardID, &cName) >= 0; iCardID ++)
152
pList = g_list_append (pList, (gpointer) cName);
158
GList *mixer_get_elements_list (void)
160
snd_mixer_elem_t *elem;
161
if (myData.mixer_handle == NULL)
166
for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
168
if (snd_mixer_selem_is_active (elem) && snd_mixer_selem_has_playback_volume (elem))
169
pList = g_list_prepend (pList, (gpointer)snd_mixer_selem_get_name (elem)); // la liste ne contiendra que des const, on ne supprimera pas ses elements lors du g_list_free.
176
static snd_mixer_elem_t *_mixer_get_element_by_name (const gchar *cName)
178
if (myData.mixer_handle == NULL)
183
snd_mixer_elem_t *elem;
184
for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
186
if (strcmp (cName, snd_mixer_selem_get_name (elem)) == 0)
191
cd_debug ("no channel matches '%s', we take the first available channel by default", cName);
192
return snd_mixer_first_elem(myData.mixer_handle);
193
/**myData.cErrorMessage = g_strdup_printf (D_("I couldn't find any audio channel named '%s'\nYou should try to open the configuration panel of the applet,\n and select the proper audio channel you want to control."), cName);
198
static int mixer_element_update_with_event (snd_mixer_elem_t *elem, unsigned int mask)
201
cd_debug ("%s (%d)", __func__, mask);
204
if (mask != SND_CTL_EVENT_MASK_REMOVE && (mask & SND_CTL_EVENT_MASK_VALUE)) // filter calls that can occur when we really don't want, like when closing the applet.
206
myData.iCurrentVolume = mixer_get_mean_volume ();
207
myData.bIsMute = mixer_is_mute ();
208
cd_debug (" iCurrentVolume <- %d bIsMute <- %d", myData.iCurrentVolume, myData.bIsMute);
216
static void mixer_get_controlled_element (void)
218
myData.pControledElement = _mixer_get_element_by_name (myConfig.cMixerElementName);
219
if (myData.pControledElement != NULL)
221
myData.bHasMuteSwitch = snd_mixer_selem_has_playback_switch (myData.pControledElement);
223
snd_mixer_selem_get_playback_volume_range (myData.pControledElement, &myData.iVolumeMin, &myData.iVolumeMax);
224
cd_debug ("volume range : %d - %d", myData.iVolumeMin, myData.iVolumeMax);
226
snd_mixer_elem_set_callback (myData.pControledElement, mixer_element_update_with_event);
228
if (myConfig.cMixerElementName2 != NULL)
230
myData.pControledElement2 = _mixer_get_element_by_name (myConfig.cMixerElementName2);
234
static int mixer_get_mean_volume (void)
236
g_return_val_if_fail (myData.pControledElement != NULL, 0);
237
long iVolumeLeft=0, iVolumeRight=0;
238
gboolean bHasLeft = snd_mixer_selem_has_playback_channel (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT);
239
gboolean bHasRight = snd_mixer_selem_has_playback_channel (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT);
240
g_return_val_if_fail (bHasLeft || bHasRight, 0);
243
snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT, &iVolumeLeft);
245
snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT, &iVolumeRight);
246
cd_debug ("volume : %d;%d", iVolumeLeft, iVolumeRight);
248
int iMeanVolume = (iVolumeLeft + iVolumeRight) / (bHasLeft + bHasRight);
250
cd_debug ("myData.iVolumeMin : %d ; myData.iVolumeMax : %d ; iMeanVolume : %d", myData.iVolumeMin, myData.iVolumeMax, iMeanVolume);
251
return (100. * (iMeanVolume - myData.iVolumeMin) / (myData.iVolumeMax - myData.iVolumeMin));
254
static void _set_mute (gboolean bMute)
256
snd_mixer_selem_set_playback_switch_all (myData.pControledElement, !bMute);
257
if (myData.pControledElement2 != NULL)
258
snd_mixer_selem_set_playback_switch_all (myData.pControledElement2, !bMute);
259
myData.bIsMute = bMute;
261
static void mixer_set_volume (int iNewVolume)
263
g_return_if_fail (myData.pControledElement != NULL);
264
cd_debug ("%s (%d)", __func__, iNewVolume);
265
int iVolume = ceil (myData.iVolumeMin + (myData.iVolumeMax - myData.iVolumeMin) * iNewVolume / 100.);
266
snd_mixer_selem_set_playback_volume_all (myData.pControledElement, iVolume);
267
if (myData.pControledElement2 != NULL)
268
snd_mixer_selem_set_playback_volume_all (myData.pControledElement2, iVolume);
269
myData.iCurrentVolume = iNewVolume;
274
cd_update_icon (); // on ne recoit pas d'evenements pour nos actions.
277
static gboolean mixer_is_mute (void)
280
g_return_val_if_fail (myData.pControledElement != NULL, FALSE);
281
if (snd_mixer_selem_has_playback_switch (myData.pControledElement))
283
int iSwitchLeft, iSwitchRight;
284
snd_mixer_selem_get_playback_switch (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT, &iSwitchLeft);
285
snd_mixer_selem_get_playback_switch (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT, &iSwitchRight);
286
cd_debug ("%d;%d", iSwitchLeft, iSwitchRight);
287
return (iSwitchLeft == 0 && iSwitchRight == 0);
293
static void mixer_switch_mute (void)
295
g_return_if_fail (myData.pControledElement != NULL);
296
gboolean bIsMute = mixer_is_mute ();
297
_set_mute (! bIsMute);
298
cd_update_icon (); // on ne recoit pas d'evenements pour nos actions.
303
static void _on_dialog_destroyed (GldiModuleInstance *myApplet)
305
myData.pDialog = NULL;
307
static void mixer_show_hide_dialog (void)
311
if (myData.pDialog == NULL)
313
const gchar *cMessage;
314
GtkWidget *pScale = NULL;
315
if (myData.cErrorMessage != NULL)
316
cMessage = myData.cErrorMessage;
319
cMessage = D_("Set up volume:");
320
pScale = mixer_build_widget (TRUE);
323
CairoDialogAttr attr;
324
memset (&attr, 0, sizeof (CairoDialogAttr));
325
attr.cText = cMessage;
326
attr.cImageFilePath = MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE;
327
attr.pInteractiveWidget = pScale;
328
attr.pUserData = myApplet;
329
attr.pFreeDataFunc = (GFreeFunc)_on_dialog_destroyed;
331
attr.pContainer = myContainer;
332
myData.pDialog = gldi_dialog_new (&attr);
336
gldi_object_unref (GLDI_OBJECT(myData.pDialog));
337
myData.pDialog = NULL;
342
static void cd_mixer_stop_alsa (void)
344
if (myData.mixer_handle != NULL)
348
g_free (myData.cErrorMessage);
349
myData.cErrorMessage = NULL;
350
g_free (myData.mixer_card_name);
351
myData.mixer_card_name = NULL;
352
g_free (myData.mixer_device_name);
353
myData.mixer_device_name= NULL;
355
if (myData.iSidCheckVolume != 0)
357
g_source_remove (myData.iSidCheckVolume);
358
myData.iSidCheckVolume = 0;
363
static gboolean mixer_check_events (gpointer data)
366
CD_APPLET_LEAVE_IF_FAIL (myData.mixer_handle, FALSE);
367
snd_mixer_handle_events (myData.mixer_handle); // ne renvoie pas d'evenements pour nos actions !
368
CD_APPLET_LEAVE(TRUE);
371
static void cd_mixer_reload_alsa (void)
375
mixer_init (myConfig.card_id);
376
mixer_get_controlled_element ();
378
if (myData.pControledElement == NULL)
380
CD_APPLET_SET_USER_IMAGE_ON_MY_ICON (myConfig.cBrokenIcon, "broken.svg");
384
mixer_element_update_with_event (myData.pControledElement, 1); // 1 => get the current state (card may have changed).
386
myData.iSidCheckVolume = g_timeout_add (1000, (GSourceFunc) mixer_check_events, (gpointer) NULL);
390
void cd_mixer_init_alsa (void)
392
// connect to the sound card
393
mixer_init (myConfig.card_id);
395
// get the mixer element
396
mixer_get_controlled_element ();
399
if (myData.pControledElement == NULL) // no luck
401
CD_APPLET_SET_USER_IMAGE_ON_MY_ICON (myConfig.cBrokenIcon, "broken.svg");
403
else // mixer aquired
406
myData.ctl.get_volume = mixer_get_mean_volume;
407
myData.ctl.set_volume = mixer_set_volume;
408
myData.ctl.toggle_mute = mixer_switch_mute;
409
myData.ctl.show_hide = mixer_show_hide_dialog;
410
myData.ctl.stop = cd_mixer_stop_alsa;
411
myData.ctl.reload = cd_mixer_reload_alsa;
413
// build the scale now if we're in a desklet
416
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
417
myData.pScale = mixer_build_widget (FALSE);
418
gtk_box_pack_end (GTK_BOX (box), myData.pScale, FALSE, FALSE, 0);
419
gtk_container_add (GTK_CONTAINER (myDesklet->container.pWidget), box);
420
gtk_widget_show_all (box);
422
if (myConfig.bHideScaleOnLeave && ! myDesklet->container.bInside)
423
gtk_widget_hide (myData.pScale);
425
else if (myIcon->cName == NULL) // in dock, set the label
427
CD_APPLET_SET_NAME_FOR_MY_ICON (myData.mixer_card_name);
430
// trigger the callback to update the icon
431
mixer_element_update_with_event (myData.pControledElement, 1); // 1 => get the current state.
432
myData.iSidCheckVolume = g_timeout_add (1000, (GSourceFunc) mixer_check_events, (gpointer) NULL);