pnmixer
Volume mixer for the system tray
audio.c
Go to the documentation of this file.
1 /* audio.c
2  * PNmixer is written by Nick Lanham, a fork of OBmixer
3  * which was programmed by Lee Ferrett, derived
4  * from the program "AbsVolume" by Paul Sherman
5  * This program is free software; you can redistribute
6  * it and/or modify it under the terms of the GNU General
7  * Public License v3. source code is available at
8  * <http://github.com/nicklan/pnmixer>
9  */
10 
21 #include <glib.h>
22 
23 #include "audio.h"
24 #include "alsa.h"
25 #include "prefs.h"
26 #include "support-log.h"
27 
28 /*
29  * Enumeration to string, for friendly debug messages.
30  */
31 
32 static const gchar *
34 {
35  switch (user) {
36  case AUDIO_USER_POPUP:
37  return "popup";
39  return "tray icon";
40  case AUDIO_USER_HOTKEYS:
41  return "hotkeys";
42  default:
43  return "unknown";
44  }
45 }
46 
47 static const gchar *
49 {
50  switch (signal) {
51  case AUDIO_NO_CARD:
52  return "no card";
54  return "card initialized";
56  return "card cleaned up";
58  return "card disconnected";
59  case AUDIO_CARD_ERROR:
60  return "card error";
62  return "values changed";
63  default:
64  return "unknown";
65  }
66 }
67 
68 /*
69  * Audio Event.
70  * An audio event is a struct that contains the current audio status.
71  * It's passed in parameters of the user signal handlers, so that
72  * user doesn't have to query the audio system to know about it.
73  * It make things a little more efficient.
74  */
75 
76 /* Free an audio event */
77 static void
79 {
80  if (event == NULL)
81  return;
82 
83  g_free(event);
84 }
85 
86 /* Create a new audio event */
87 static AudioEvent *
89 {
90  AudioEvent *event;
91 
92  /* At the moment there's no need to duplicate card/channel
93  * name strings, so let's optimize a very little and make
94  * them const pointers.
95  */
96 
97  event = g_new0(AudioEvent, 1);
98 
99  event->signal = signal;
100  event->user = user;
101  event->card = audio_get_card(audio);
102  event->channel = audio_get_channel(audio);
103  event->has_mute = audio_has_mute(audio);
104  event->muted = audio_is_muted(audio);
105  event->volume = audio_get_volume(audio);
106 
107  return event;
108 }
109 
110 /*
111  * Audio Signal Handlers.
112  * An audio signal handler is made of a callback and a data pointer.
113  * It's the basic type that holds user signal handlers.
114  */
115 
118  gpointer data;
119 };
120 
122 
123 /* Free an audio handler */
124 static void
126 {
127  g_free(handler);
128 }
129 
130 /* Create a new audio handler */
131 static AudioHandler *
133 {
134  AudioHandler *handler;
135 
136  handler = g_new0(AudioHandler, 1);
137  handler->callback = callback;
138  handler->data = data;
139 
140  return handler;
141 }
142 
143 /* Check if two handlers are the identical. We compare the content
144  * of the structures, not the pointers.
145  */
146 static gint
148 {
149  if (h1->callback == h2->callback && h1->data == h2->data)
150  return 0;
151  return -1;
152 }
153 
154 /* Add a handler to a handler list */
155 static GSList *
157 {
158  GSList *item;
159 
160  /* Ensure that the handler is not already part of the list.
161  * It's probably an error to have a duplicated handler.
162  */
163  item = g_slist_find_custom(list, handler, (GCompareFunc) audio_handler_cmp);
164  if (item) {
165  WARN("Audio handler already in the list");
166  return list;
167  }
168 
169  /* Append the handler to the list */
170  return g_slist_append(list, handler);
171 }
172 
173 /* Remove a handler from a handler list */
174 static GSList *
176 {
177  GSList *item;
178 
179  /* Find the handler */
180  item = g_slist_find_custom(list, handler, (GCompareFunc) audio_handler_cmp);
181  if (item == NULL) {
182  WARN("Audio handler wasn't found in the list");
183  return list;
184  }
185 
186  /* Remove the handler from the list.
187  * We assume there's only one such handler.
188  */
189  list = g_slist_remove_link(list, item);
190  g_slist_free_full(item, (GDestroyNotify) audio_handler_free);
191  return list;
192 }
193 
194 /*
195  * Public functions & signals handlers
196  */
197 
198 struct audio {
199  /* Preferences */
200  gdouble scroll_step;
201  gboolean normalize;
202  /* Underlying sound card */
204  /* Cached value (to avoid querying the underlying
205  * sound card each time we need the info).
206  */
207  gchar *card;
208  gchar *channel;
209  /* Last action performed (volume/mute change) */
211  /* User signal handlers.
212  * To be invoked when the audio status changes.
213  */
214  GSList *handlers;
215 };
216 
224 static void
226 {
227  AudioEvent *event;
228  GSList *item;
229 
230  /* Nothing to do if there is no handlers */
231  if (audio->handlers == NULL)
232  return;
233 
234  /* Create a new event */
235  event = audio_event_new(audio, signal, user);
236 
237  /* Invoke the various handlers around */
238  DEBUG("** Dispatching signal '%s' from '%s', vol=%lg, has_mute=%s, muted=%s",
239  audio_signal_to_str(signal), audio_user_to_str(user),
240  event->volume, event->has_mute ? "yes" : "no", event->muted ? "yes" : "no");
241 
242  for (item = audio->handlers; item; item = item->next) {
243  AudioHandler *handler = item->data;
244  handler->callback(audio, event, handler->data);
245  }
246 
247  /* Then free the event */
248  audio_event_free(event);
249 }
250 
257 static void
258 on_alsa_event(enum alsa_event event, gpointer data)
259 {
260  Audio *audio = (Audio *) data;
261 
262  /* If we are responsible for this event (aka we changed the volume/mute
263  * values beforehand), we know that we left a timestamp to indicate
264  * when the action was performed.
265  */
266  if (audio->last_action_timestamp != 0) {
267  gint64 last, now, delay;
268 
269  last = audio->last_action_timestamp;
270  now = g_get_monotonic_time();
271 
272  /* Discard the timestamp */
273  audio->last_action_timestamp = 0;
274 
275  /* The delay here is the time between the moment the action
276  * was performed and the moment the callback was invoked.
277  */
278  delay = now - last;
279 
280  /* The delay is supposed to be very quick, a matter of milliseconds.
281  * We set its maximum delay to 1 second, it's probably far too much.
282  * However it doesn't hurt to set it too long. On the other hand,
283  * setting it too short could hurt.
284  */
285  if (delay < 1000000)
286  return;
287 
288  /* In some situation we can find a timestamp that was never used */
289  DEBUG("Discarding last timestamp, too old");
290  }
291 
292  /* Here, we are not at the origin of this change.
293  * We must invoke the handlers.
294  */
295  switch (event) {
296  case ALSA_CARD_ERROR:
298  break;
301  break;
304  break;
305  default:
306  WARN("Unhandled alsa event: %d", event);
307  }
308 }
309 
317 void
319 {
320  AudioHandler *handler;
321 
322  handler = audio_handler_new(callback, data);
323  audio->handlers = audio_handler_list_remove(audio->handlers, handler);
324  audio_handler_free(handler);
325 }
326 
336 void
338 {
339  AudioHandler *handler;
340 
341  handler = audio_handler_new(callback, data);
342  audio->handlers = audio_handler_list_append(audio->handlers, handler);
343 }
344 
352 const char *
354 {
355  return audio->card;
356 }
357 
365 const char *
367 {
368  return audio->channel;
369 }
370 
377 gboolean
379 {
380  AlsaCard *soundcard = audio->soundcard;
381 
382  if (!soundcard)
383  return FALSE;
384 
385  return alsa_card_has_mute(soundcard);
386 }
387 
394 gboolean
396 {
397  AlsaCard *soundcard = audio->soundcard;
398 
399  if (!soundcard)
400  return TRUE;
401 
402  return alsa_card_is_muted(soundcard);
403 }
404 
411 void
413 {
414  AlsaCard *soundcard = audio->soundcard;
415 
416  /* Discard if no soundcard available */
417  if (!soundcard)
418  return;
419 
420  /* Leave a trace */
421  audio->last_action_timestamp = g_get_real_time();
422 
423  /* Toggle mute state */
424  alsa_card_toggle_mute(soundcard);
425 
426  /* Invoke the handlers */
428 }
429 
436 gdouble
438 {
439  AlsaCard *soundcard = audio->soundcard;
440 
441  if (!soundcard)
442  return 0;
443 
444  return alsa_card_get_volume(soundcard);
445 }
446 
457 void
458 _audio_set_volume(Audio *audio, AudioUser user, gdouble cur_volume,
459  gdouble new_volume, gint dir)
460 {
461  AlsaCard *soundcard = audio->soundcard;
462 
463  /* Discard if no soundcard available */
464  if (!soundcard)
465  return;
466 
467  /* Set the volume */
468  DEBUG("Setting volume from %lg to %lg (dir: %d)",
469  cur_volume, new_volume, dir);
470  alsa_card_set_volume(soundcard, new_volume, dir);
471 
472  /* Automatically unmute the volume */
473  if (alsa_card_is_muted(soundcard))
474  alsa_card_toggle_mute(soundcard);
475 
476  /* Check if the volume really changed. If it doesn't,
477  * there's no need to invoke any handlers. It also means
478  * that no alsa callback will be triggered, so we don't
479  * save the 'last_action_timestamp'.
480  */
481  new_volume = alsa_card_get_volume(soundcard);
482  if (new_volume == cur_volume)
483  return;
484 
485  /* Leave a trace */
486  audio->last_action_timestamp = g_get_real_time();
487 
488  /* Invoke handlers manually.
489  * In theory, we could skip this step, since the Alsa callback
490  * will be triggered anyway after the volume change is effective,
491  * and we could invoke the handlers at this moment.
492  * In practice, relying on the Alsa callback is not so reliable.
493  * It seems that it's kind of broken if PulseAudio is running.
494  * So, invoking the handlers at this point makes PNMixer more robust.
495  */
497 }
498 
499 void
500 audio_set_volume(Audio *audio, AudioUser user, gdouble new_volume, gint dir)
501 {
502  AlsaCard *soundcard = audio->soundcard;
503  gdouble cur_volume;
504 
505  cur_volume = alsa_card_get_volume(soundcard);
506  _audio_set_volume(audio, user, cur_volume, new_volume, dir);
507 }
508 
515 void
517 {
518  AlsaCard *soundcard = audio->soundcard;
519  gdouble scroll_step = audio->scroll_step;
520  gdouble cur_volume, new_volume;
521 
522  cur_volume = alsa_card_get_volume(soundcard);
523  new_volume = cur_volume - scroll_step;
524  if (new_volume < 0)
525  new_volume = 0;
526  _audio_set_volume(audio, user, cur_volume, new_volume, -1);
527 }
528 
535 void
537 {
538  AlsaCard *soundcard = audio->soundcard;
539  gdouble scroll_step = audio->scroll_step;
540  gdouble cur_volume, new_volume;
541 
542  cur_volume = alsa_card_get_volume(soundcard);
543  new_volume = cur_volume + scroll_step;
544  if (new_volume > 100)
545  new_volume = 100;
546  _audio_set_volume(audio, user, cur_volume, new_volume, +1);
547 }
548 
554 static void
556 {
557  if (audio->soundcard == NULL)
558  return;
559 
560  DEBUG("Unhooking soundcard from the audio system");
561 
562  /* Free the soundcard */
563  alsa_card_free(audio->soundcard);
564  audio->soundcard = NULL;
565 
566  /* Invoke user handlers */
568 }
569 
578 static void
580 {
581  AlsaCard *soundcard;
582  GSList *card_list, *item;
583 
584  g_assert(audio->soundcard == NULL);
585 
586  /* Attempt to create the card */
587  DEBUG("Hooking soundcard '%s (%s)' to the audio system", audio->card, audio->channel);
588 
589  soundcard = alsa_card_new(audio->card, audio->channel, audio->normalize);
590  if (soundcard)
591  goto end;
592 
593  /* On failure, try to create the card from the list of available cards.
594  * We don't try with the card name that just failed.
595  */
596  DEBUG("Could not hook soundcard, trying every card available");
597 
598  card_list = alsa_list_cards();
599  item = g_slist_find_custom(card_list, audio->card, (GCompareFunc) g_strcmp0);
600  if (item) {
601  DEBUG("Removing '%s' from card list", (char *) item->data);
602  card_list = g_slist_remove(card_list, item);
603  g_slist_free_full(item, g_free);
604  }
605 
606  /* Now iterate on card list and attempt to get a working soundcard */
607  for (item = card_list; item; item = item->next) {
608  const char *card = item->data;
609  char *channel;
610 
611  channel = prefs_get_channel(card);
612  soundcard = alsa_card_new(card, channel, audio->normalize);
613  g_free(channel);
614 
615  if (soundcard)
616  break;
617  }
618 
619  /* Free card list */
620  g_slist_free_full(card_list, g_free);
621 
622 end:
623  /* Save soundcard NOW !
624  * We're going to invoke handlers later on, and these guys
625  * need a valid soundcard pointer.
626  */
627  audio->soundcard = soundcard;
628 
629  /* Finish making everything ready */
630  if (soundcard == NULL) {
631  DEBUG("No soundcard could be hooked !");
632 
633  /* Card and channel names set to emptry string */
634  g_free(audio->card);
635  audio->card = g_strdup("");
636  g_free(audio->channel);
637  audio->channel = g_strdup("");
638 
639  /* Tell the world */
641  } else {
642  DEBUG("Soundcard successfully hooked (scroll step: %lg, normalize: %s)",
643  audio->scroll_step, audio->normalize ? "true" : "false");
644 
645  /* Card and channel names must match the truth.
646  * Indeed, in case of failure, we may end up using a soundcard
647  * different from the one specified in the preferences.
648  */
649  g_free(audio->card);
650  audio->card = g_strdup(alsa_card_get_name(soundcard));
651  g_free(audio->channel);
652  audio->channel = g_strdup(alsa_card_get_channel(soundcard));
653 
654  /* Install callbacks */
655  alsa_card_install_callback(soundcard, on_alsa_event, audio);
656 
657  /* Tell the world */
659  }
660 }
661 
668 void
670 {
671  /* Get preferences */
672  g_free(audio->card);
673  audio->card = prefs_get_string("AlsaCard", NULL);
674  g_free(audio->channel);
675  audio->channel = prefs_get_channel(audio->card);
676  audio->normalize = prefs_get_boolean("NormalizeVolume", TRUE);
677  audio->scroll_step = prefs_get_double("ScrollStep", 5);
678 
679  /* Rehook soundcard */
680  audio_unhook_soundcard(audio);
681  audio_hook_soundcard(audio);
682 }
683 
690 void
692 {
693  if (audio == NULL)
694  return;
695 
696  audio_unhook_soundcard(audio);
697  g_free(audio->channel);
698  g_free(audio->card);
699  g_free(audio);
700 }
701 
709 Audio *
711 {
712  Audio *audio;
713 
714  audio = g_new0(Audio, 1);
715 
716  return audio;
717 }
718 
725 GSList *
727 {
728  return alsa_list_cards();
729 }
730 
738 GSList *
739 audio_get_channel_list(const char *card_name)
740 {
741  return alsa_list_channels(card_name);
742 }
743 
Logging support.
Header for audio.c.
gdouble scroll_step
Definition: audio.c:200
void audio_raise_volume(Audio *audio, AudioUser user)
Definition: audio.c:536
enum audio_signal AudioSignal
Definition: audio.h:71
enum audio_user AudioUser
Definition: audio.h:46
static void audio_hook_soundcard(Audio *audio)
Definition: audio.c:579
gboolean alsa_card_has_mute(AlsaCard *card)
Definition: alsa.c:762
const char * audio_get_card(Audio *audio)
Definition: audio.c:353
static GSList * audio_handler_list_remove(GSList *list, AudioHandler *handler)
Definition: audio.c:175
gboolean normalize
Definition: audio.c:201
gint64 last_action_timestamp
Definition: audio.c:210
static Audio * audio
Definition: main.c:40
const char * alsa_card_get_channel(AlsaCard *card)
Definition: alsa.c:750
void audio_signals_disconnect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:318
Header for alsa.c.
static void audio_unhook_soundcard(Audio *audio)
Definition: audio.c:555
gboolean audio_has_mute(Audio *audio)
Definition: audio.c:378
gdouble alsa_card_get_volume(AlsaCard *card)
Definition: alsa.c:804
void audio_toggle_mute(Audio *audio, AudioUser user)
Definition: audio.c:412
void audio_signals_connect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:337
gboolean audio_is_muted(Audio *audio)
Definition: audio.c:395
Header for prefs.c.
gpointer data
Definition: audio.c:118
gboolean muted
Definition: audio.h:79
static AudioHandler * audio_handler_new(AudioCallback callback, gpointer data)
Definition: audio.c:132
GSList * alsa_list_cards(void)
Definition: alsa.c:966
static void on_alsa_event(enum alsa_event event, gpointer data)
Definition: audio.c:258
gchar * channel
Definition: audio.c:208
gboolean prefs_get_boolean(const gchar *key, gboolean def)
Definition: prefs.c:102
#define DEBUG(...)
Definition: support-log.h:38
alsa_event
Definition: alsa.h:30
const char * audio_get_channel(Audio *audio)
Definition: audio.c:366
void alsa_card_toggle_mute(AlsaCard *card)
Definition: alsa.c:788
gboolean has_mute
Definition: audio.h:78
static void audio_event_free(AudioEvent *event)
Definition: audio.c:78
GSList * audio_get_channel_list(const char *card_name)
Definition: audio.c:739
void alsa_card_free(AlsaCard *card)
Definition: alsa.c:862
Audio * audio_new(void)
Definition: audio.c:710
static AudioEvent * audio_event_new(Audio *audio, AudioSignal signal, AudioUser user)
Definition: audio.c:88
gboolean alsa_card_is_muted(AlsaCard *card)
Definition: alsa.c:774
Definition: audio.c:198
static void audio_handler_free(AudioHandler *handler)
Definition: audio.c:125
gchar * prefs_get_channel(const gchar *card)
Definition: prefs.c:259
GSList * alsa_list_channels(const char *card_name)
Definition: alsa.c:1002
static gint audio_handler_cmp(AudioHandler *h1, AudioHandler *h2)
Definition: audio.c:147
gchar * card
Definition: audio.c:207
gchar * prefs_get_string(const gchar *key, const gchar *def)
Definition: prefs.c:171
gdouble audio_get_volume(Audio *audio)
Definition: audio.c:437
void(* AudioCallback)(Audio *audio, AudioEvent *event, gpointer data)
Definition: audio.h:85
GSList * audio_get_card_list(void)
Definition: audio.c:726
static const gchar * audio_user_to_str(AudioUser user)
Definition: audio.c:33
AlsaCard * alsa_card_new(const char *card_name, const char *channel, gboolean normalize)
Definition: alsa.c:891
void alsa_card_set_volume(AlsaCard *card, gdouble value, int dir)
Definition: alsa.c:827
static GSList * audio_handler_list_append(GSList *list, AudioHandler *handler)
Definition: audio.c:156
void audio_set_volume(Audio *audio, AudioUser user, gdouble new_volume, gint dir)
Definition: audio.c:500
void alsa_card_install_callback(AlsaCard *card, AlsaCb callback, gpointer user_data)
Definition: alsa.c:850
static void invoke_handlers(Audio *audio, AudioSignal signal, AudioUser user)
Definition: audio.c:225
AudioCallback callback
Definition: audio.c:117
void audio_free(Audio *audio)
Definition: audio.c:691
void audio_reload(Audio *audio)
Definition: audio.c:669
void audio_lower_volume(Audio *audio, AudioUser user)
Definition: audio.c:516
GSList * handlers
Definition: audio.c:214
const char * alsa_card_get_name(AlsaCard *card)
Definition: alsa.c:737
AlsaCard * soundcard
Definition: audio.c:203
gdouble volume
Definition: audio.h:80
#define WARN(...)
Definition: support-log.h:37
gdouble prefs_get_double(const gchar *key, gdouble def)
Definition: prefs.c:148
void _audio_set_volume(Audio *audio, AudioUser user, gdouble cur_volume, gdouble new_volume, gint dir)
Definition: audio.c:458
static const gchar * audio_signal_to_str(AudioSignal signal)
Definition: audio.c:48