~cairo-dock-team/cairo-dock-plug-ins/plug-ins

« back to all changes in this revision

Viewing changes to alsaMixer/src/applet-backend-alsamixer.c

  • Committer: Matthieu Baerts
  • Date: 2014-10-19 00:26:10 UTC
  • Revision ID: matttbe@gmail.com-20141019002610-ulf26s9b4c4rw10r
We just switched from BZR to Git.
Follow us on Github: https://github.com/Cairo-Dock

Note: we will only use Github to manage our source code and all pull requests.
Please continue to report your bugs/ideas/messages on our forum or Launchpad! 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**
2
 
* This file is a part of the Cairo-Dock project
3
 
*
4
 
* Copyright : (C) see the 'copyright' file.
5
 
* E-mail    : see the 'copyright' file.
6
 
*
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.
11
 
*
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/>.
18
 
*/
19
 
 
20
 
#include <stdlib.h>
21
 
#include <math.h>
22
 
#include <string.h>
23
 
#include <glib/gi18n.h>
24
 
 
25
 
#include "applet-struct.h"
26
 
#include "applet-draw.h"
27
 
#include "applet-generic.h"
28
 
#include "applet-backend-alsamixer.h"
29
 
 
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);
34
 
 
35
 
static gchar *_mixer_get_card_id_from_name (const gchar *cName)
36
 
{
37
 
        if (cName == NULL)
38
 
                return g_strdup ("default");
39
 
        
40
 
        int iCardID = -1;
41
 
        char *cName2;
42
 
        while (snd_card_next (&iCardID) == 0 && iCardID != -1)
43
 
        {
44
 
                snd_card_get_name (iCardID, &cName2);
45
 
                cd_debug ("+ card %d: %s", iCardID, cName2);
46
 
                if (! cName2)
47
 
                        continue;
48
 
                if (strcmp (cName2, cName) == 0)
49
 
                {
50
 
                        free (cName2);
51
 
                        return g_strdup_printf ("hw:%d", iCardID);
52
 
                }
53
 
                free (cName2);
54
 
        }
55
 
        return g_strdup ("default");
56
 
}
57
 
 
58
 
static void mixer_init (const gchar *cCardName)  // this function is taken from AlsaMixer.
59
 
{
60
 
        snd_ctl_card_info_t *hw_info = NULL;  // ne pas liberer.
61
 
        snd_ctl_t *ctl_handle = NULL;
62
 
        int err;
63
 
        snd_ctl_card_info_alloca (&hw_info);
64
 
        
65
 
        // get the card ID
66
 
        gchar *cCardID = _mixer_get_card_id_from_name (cCardName);
67
 
        
68
 
        // get the card info
69
 
        if ((err = snd_ctl_open (&ctl_handle, cCardID, 0)) < 0)
70
 
        {
71
 
                myData.cErrorMessage = g_strdup_printf (D_("I couldn't open card '%s'"), cCardID);
72
 
                g_free (cCardID);
73
 
                return ;
74
 
        }
75
 
        if ((err = snd_ctl_card_info (ctl_handle, hw_info)) < 0)
76
 
        {
77
 
                myData.cErrorMessage = g_strdup_printf (D_("Card '%s' opened but I couldn't get any info"), cCardID);
78
 
                g_free (cCardID);
79
 
                return ;
80
 
        }
81
 
        snd_ctl_close (ctl_handle);
82
 
        
83
 
        // open mixer device
84
 
        if ((err = snd_mixer_open (&myData.mixer_handle, 0)) < 0)
85
 
        {
86
 
                myData.cErrorMessage = g_strdup (D_("I couldn't open the mixer"));
87
 
                g_free (cCardID);
88
 
                return ;
89
 
        }
90
 
        if (mixer_level == 0 && (err = snd_mixer_attach (myData.mixer_handle, cCardID)) < 0)
91
 
        {
92
 
                snd_mixer_free (myData.mixer_handle);
93
 
                myData.mixer_handle = NULL;
94
 
                g_free (cCardID);
95
 
                myData.cErrorMessage = g_strdup (D_("I couldn't attach the mixer to the card"));
96
 
                return ;
97
 
        }
98
 
        if ((err = snd_mixer_selem_register (myData.mixer_handle, mixer_level > 0 ? &mixer_options : NULL, NULL)) < 0)
99
 
        {
100
 
                snd_mixer_free (myData.mixer_handle);
101
 
                myData.mixer_handle = NULL;
102
 
                g_free (cCardID);
103
 
                myData.cErrorMessage = g_strdup (D_("I couldn't register options"));
104
 
                return ;
105
 
        }
106
 
        if ((err = snd_mixer_load (myData.mixer_handle)) < 0)
107
 
        {
108
 
                snd_mixer_free (myData.mixer_handle);
109
 
                myData.mixer_handle = NULL;
110
 
                g_free (cCardID);
111
 
                myData.cErrorMessage = g_strdup (D_("I couldn't load the mixer"));
112
 
                return ;
113
 
        }
114
 
        
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);
118
 
        g_free (cCardID);
119
 
}
120
 
 
121
 
void mixer_stop (void)
122
 
{
123
 
        if (myData.mixer_handle != NULL)
124
 
        {
125
 
                gchar *cCardID = _mixer_get_card_id_from_name (myConfig.card_id);
126
 
                snd_mixer_detach (myData.mixer_handle, cCardID);
127
 
                g_free (cCardID);
128
 
                snd_mixer_close (myData.mixer_handle);
129
 
                myData.mixer_handle = NULL;
130
 
                myData.pControledElement = NULL;
131
 
                myData.pControledElement2 = NULL;
132
 
                
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;
139
 
        }
140
 
}
141
 
 
142
 
 
143
 
GList *mixer_get_cards_list (void)
144
 
{
145
 
        int iCardID;
146
 
        char *cName;
147
 
        GList *pList = NULL;
148
 
        
149
 
        pList = g_list_append (pList, (gpointer) g_strdup(""));
150
 
        for (iCardID = 0; snd_card_get_name (iCardID, &cName) >= 0; iCardID ++)
151
 
        {
152
 
                pList = g_list_append (pList, (gpointer) cName);
153
 
        }
154
 
        return pList;
155
 
}
156
 
 
157
 
 
158
 
GList *mixer_get_elements_list (void)
159
 
{
160
 
        snd_mixer_elem_t *elem;
161
 
        if (myData.mixer_handle == NULL)
162
 
                return NULL;
163
 
        cd_message ("");
164
 
        
165
 
        GList *pList = NULL;
166
 
        for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
167
 
        {
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.
170
 
        }
171
 
        return pList;
172
 
}
173
 
 
174
 
 
175
 
 
176
 
static snd_mixer_elem_t *_mixer_get_element_by_name (const gchar *cName)
177
 
{
178
 
        if (myData.mixer_handle == NULL)
179
 
                return NULL;
180
 
        
181
 
        if (cName != NULL)
182
 
        {
183
 
                snd_mixer_elem_t *elem;
184
 
                for (elem = snd_mixer_first_elem(myData.mixer_handle); elem; elem = snd_mixer_elem_next(elem))
185
 
                {
186
 
                        if (strcmp (cName, snd_mixer_selem_get_name (elem)) == 0)
187
 
                                return elem;
188
 
                }
189
 
        }
190
 
        
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);
194
 
        return NULL;*/
195
 
}
196
 
 
197
 
 
198
 
static int mixer_element_update_with_event (snd_mixer_elem_t *elem, unsigned int mask)
199
 
{
200
 
        CD_APPLET_ENTER;
201
 
        cd_debug ("%s (%d)", __func__, mask);
202
 
        
203
 
        
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.
205
 
        {
206
 
                myData.iCurrentVolume = mixer_get_mean_volume ();
207
 
                myData.bIsMute = mixer_is_mute ();
208
 
                cd_debug (" iCurrentVolume <- %d bIsMute <- %d", myData.iCurrentVolume, myData.bIsMute);
209
 
                
210
 
                cd_update_icon ();
211
 
        }
212
 
        
213
 
        CD_APPLET_LEAVE(0);
214
 
}
215
 
 
216
 
static void mixer_get_controlled_element (void)
217
 
{
218
 
        myData.pControledElement = _mixer_get_element_by_name (myConfig.cMixerElementName);
219
 
        if (myData.pControledElement != NULL)
220
 
        {
221
 
                myData.bHasMuteSwitch = snd_mixer_selem_has_playback_switch (myData.pControledElement);
222
 
                
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);
225
 
                
226
 
                snd_mixer_elem_set_callback (myData.pControledElement, mixer_element_update_with_event);
227
 
        }
228
 
        if (myConfig.cMixerElementName2 != NULL)
229
 
        {
230
 
                myData.pControledElement2 = _mixer_get_element_by_name (myConfig.cMixerElementName2);
231
 
        }
232
 
}
233
 
 
234
 
static int mixer_get_mean_volume (void)
235
 
{
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);
241
 
        
242
 
        if (bHasLeft)
243
 
                snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_LEFT, &iVolumeLeft);
244
 
        if (bHasRight)
245
 
                snd_mixer_selem_get_playback_volume (myData.pControledElement, SND_MIXER_SCHN_FRONT_RIGHT, &iVolumeRight);
246
 
        cd_debug ("volume : %d;%d", iVolumeLeft, iVolumeRight);
247
 
        
248
 
        int iMeanVolume = (iVolumeLeft + iVolumeRight) / (bHasLeft + bHasRight);
249
 
        
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));
252
 
}
253
 
 
254
 
static void _set_mute (gboolean bMute)
255
 
{
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;
260
 
}
261
 
static void mixer_set_volume (int iNewVolume)
262
 
{
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;
270
 
        if (myData.bIsMute)
271
 
        {
272
 
                _set_mute (FALSE);
273
 
        }
274
 
        cd_update_icon ();  // on ne recoit pas d'evenements pour nos actions.
275
 
}
276
 
 
277
 
static gboolean mixer_is_mute (void)
278
 
{
279
 
        cd_debug ("");
280
 
        g_return_val_if_fail (myData.pControledElement != NULL, FALSE);
281
 
        if (snd_mixer_selem_has_playback_switch (myData.pControledElement))
282
 
        {
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);
288
 
        }
289
 
        else
290
 
                return FALSE;
291
 
}
292
 
 
293
 
static void mixer_switch_mute (void)
294
 
{
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.
299
 
}
300
 
 
301
 
 
302
 
 
303
 
static void _on_dialog_destroyed (GldiModuleInstance *myApplet)
304
 
{
305
 
        myData.pDialog = NULL;
306
 
}
307
 
static void mixer_show_hide_dialog (void)
308
 
{
309
 
        if (myDesklet)
310
 
                return ;
311
 
        if (myData.pDialog == NULL)
312
 
        {
313
 
                const gchar *cMessage;
314
 
                GtkWidget *pScale = NULL;
315
 
                if (myData.cErrorMessage != NULL)
316
 
                        cMessage = myData.cErrorMessage;
317
 
                else
318
 
                {
319
 
                        cMessage = D_("Set up volume:");
320
 
                        pScale = mixer_build_widget (TRUE);
321
 
                }
322
 
                
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;
330
 
                attr.pIcon = myIcon;
331
 
                attr.pContainer = myContainer;
332
 
                myData.pDialog = gldi_dialog_new (&attr);
333
 
        }
334
 
        else
335
 
        {
336
 
                gldi_object_unref (GLDI_OBJECT(myData.pDialog));
337
 
                myData.pDialog = NULL;
338
 
        }
339
 
}
340
 
 
341
 
 
342
 
static void cd_mixer_stop_alsa (void)
343
 
{
344
 
        if (myData.mixer_handle != NULL)
345
 
        {
346
 
                mixer_stop ();
347
 
                
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;
354
 
                
355
 
                if (myData.iSidCheckVolume != 0)
356
 
                {
357
 
                        g_source_remove (myData.iSidCheckVolume);
358
 
                        myData.iSidCheckVolume = 0;
359
 
                }
360
 
        }
361
 
}
362
 
 
363
 
static gboolean mixer_check_events (gpointer data)
364
 
{
365
 
        CD_APPLET_ENTER;
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);
369
 
}
370
 
 
371
 
static void cd_mixer_reload_alsa (void)
372
 
{
373
 
        myData.ctl.stop ();
374
 
        
375
 
        mixer_init (myConfig.card_id);
376
 
        mixer_get_controlled_element ();
377
 
        
378
 
        if (myData.pControledElement == NULL)
379
 
        {
380
 
                CD_APPLET_SET_USER_IMAGE_ON_MY_ICON (myConfig.cBrokenIcon, "broken.svg");
381
 
        }
382
 
        else
383
 
        {
384
 
                mixer_element_update_with_event (myData.pControledElement, 1);  // 1 => get the current state (card may have changed).
385
 
                
386
 
                myData.iSidCheckVolume = g_timeout_add (1000, (GSourceFunc) mixer_check_events, (gpointer) NULL);
387
 
        }
388
 
}
389
 
 
390
 
void cd_mixer_init_alsa (void)
391
 
{
392
 
        // connect to the sound card
393
 
        mixer_init (myConfig.card_id);
394
 
        
395
 
        // get the mixer element
396
 
        mixer_get_controlled_element ();
397
 
        
398
 
        // update the icon
399
 
        if (myData.pControledElement == NULL)  // no luck
400
 
        {
401
 
                CD_APPLET_SET_USER_IMAGE_ON_MY_ICON (myConfig.cBrokenIcon, "broken.svg");
402
 
        }
403
 
        else  // mixer aquired
404
 
        {
405
 
                // set the interface
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;
412
 
                
413
 
                // build the scale now if we're in a desklet
414
 
                if (myDesklet)
415
 
                {
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);
421
 
                        
422
 
                        if (myConfig.bHideScaleOnLeave && ! myDesklet->container.bInside)
423
 
                                gtk_widget_hide (myData.pScale);
424
 
                }
425
 
                else if (myIcon->cName == NULL)  // in dock, set the label
426
 
                {
427
 
                        CD_APPLET_SET_NAME_FOR_MY_ICON (myData.mixer_card_name);
428
 
                }
429
 
                
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);
433
 
        }
434
 
}