pulse.c 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #include "../../thread_helper.h"
  2. #include <err.h>
  3. #include <time.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <stdlib.h>
  8. #include <pulse/pulseaudio.h>
  9. #ifndef USE_PULSE
  10. # define USE_PULSE
  11. #endif
  12. #include "../volume.h"
  13. #include "../../lib/util.h"
  14. #include "../../aslstatus.h"
  15. #include "../../components_config.h"
  16. #ifndef VOLUME_SYM
  17. # define VOLUME_SYM ""
  18. #endif
  19. #ifndef VOLUME_PERCENT
  20. # define VOLUME_PERCENT " %"
  21. #endif
  22. #ifndef VOLUME_MUTED
  23. # define VOLUME_MUTED "muted"
  24. #endif
  25. #ifndef VOLUME_PULSE_PROC_NAME
  26. # define VOLUME_PULSE_PROC_NAME "volume:pulse"
  27. #endif
  28. #ifndef VOLUME_PULSE_RECONECT_TIMEOUT
  29. # define VOLUME_PULSE_RECONECT_TIMEOUT 5000
  30. #endif
  31. static void *mainloop_thread(void *);
  32. static void pulse_cleanup(void *ptr);
  33. void
  34. vol_perc(char *volume,
  35. const char __unused *_a,
  36. uint32_t __unused _i,
  37. static_data_t *static_data)
  38. {
  39. struct volume_static_data *data = static_data->data;
  40. static const struct timespec ts = {
  41. .tv_sec = MS2S(VOLUME_PULSE_RECONECT_TIMEOUT),
  42. .tv_nsec = MS2NS(VOLUME_PULSE_RECONECT_TIMEOUT),
  43. };
  44. if (!static_data->cleanup) static_data->cleanup = pulse_cleanup;
  45. if (!data->pulse_thread_started) {
  46. data->pulse_thread_started = !0;
  47. data->out = volume;
  48. data->volume_thread = pthread_self();
  49. pthread_create(&data->pulse_thread,
  50. NULL,
  51. mainloop_thread,
  52. data);
  53. pthread_setname(data->pulse_thread, VOLUME_PULSE_PROC_NAME);
  54. } else {
  55. for (;;) {
  56. if (!!data->pulse_thread
  57. && !pthread_tryjoin_np(data->pulse_thread, NULL)) {
  58. data->pulse_thread_started = 0;
  59. ERRRET(volume);
  60. }
  61. if (!!nanosleep(&ts, NULL))
  62. /* if interrupted by signal */
  63. break;
  64. }
  65. }
  66. }
  67. static void
  68. quit(int ret, pa_mainloop_api *mainloop)
  69. {
  70. assert(mainloop);
  71. mainloop->quit(mainloop, ret);
  72. }
  73. static void
  74. sink_info_callback(pa_context __unused *_c,
  75. const pa_sink_info *i,
  76. int __unused _is_last,
  77. void *userdata)
  78. {
  79. struct volume_static_data *data = userdata;
  80. if (!i) return;
  81. if (i->mute)
  82. bprintf(data->out, "%s", VOLUME_MUTED);
  83. else
  84. bprintf(data->out,
  85. "%s%" PRIperc "%s",
  86. VOLUME_SYM,
  87. (percent_t)((pa_cvolume_avg(&(i->volume)) * 100
  88. + (pa_volume_t)PA_VOLUME_NORM / 2)
  89. / (pa_volume_t)PA_VOLUME_NORM),
  90. VOLUME_PERCENT);
  91. pthread_kill(data->volume_thread, SIGUSR1);
  92. }
  93. static void
  94. subscribe_callback(pa_context *c,
  95. pa_subscription_event_type_t type,
  96. uint32_t idx,
  97. void *userdata)
  98. {
  99. switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
  100. case PA_SUBSCRIPTION_EVENT_SINK:
  101. pa_operation_unref(
  102. pa_context_get_sink_info_by_index(c,
  103. idx,
  104. sink_info_callback,
  105. userdata));
  106. }
  107. }
  108. static void
  109. server_info_callback(pa_context *c, const pa_server_info *i, void *userdata)
  110. {
  111. pa_context_get_sink_info_by_name(c,
  112. i->default_sink_name,
  113. sink_info_callback,
  114. userdata);
  115. }
  116. static void
  117. context_state_callback(pa_context *c, void *userdata)
  118. {
  119. struct volume_static_data *data = userdata;
  120. switch (pa_context_get_state(c)) {
  121. case PA_CONTEXT_CONNECTING:
  122. case PA_CONTEXT_AUTHORIZING:
  123. case PA_CONTEXT_SETTING_NAME:
  124. break;
  125. case PA_CONTEXT_READY:
  126. pa_context_get_server_info(c, server_info_callback, data);
  127. /*
  128. * Subscribe to sink events from the server. This is how we get
  129. * volume change notifications from the server.
  130. */
  131. pa_context_set_subscribe_callback(c, subscribe_callback, data);
  132. pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
  133. break;
  134. case PA_CONTEXT_TERMINATED:
  135. warnx("PulseAudio connection terminated.\n");
  136. quit(!0, data->mainloop);
  137. break;
  138. case PA_CONTEXT_FAILED:
  139. default:
  140. warnx("Connection failure: %s",
  141. pa_strerror(pa_context_errno(c)));
  142. quit(!0, data->mainloop);
  143. }
  144. }
  145. void *
  146. mainloop_thread(void *userdata)
  147. {
  148. pa_mainloop *m;
  149. int ret = 1;
  150. pa_context *context = NULL;
  151. struct volume_static_data *data = userdata;
  152. if (!(m = pa_mainloop_new())) {
  153. warnx("pa_mainloop_new() failed");
  154. goto quit;
  155. }
  156. data->mainloop = pa_mainloop_get_api(m);
  157. assert(pa_signal_init(data->mainloop) == 0);
  158. if (!(context = pa_context_new(data->mainloop,
  159. "Aslstatus pulseaudio widget"))) {
  160. warnx("pa_context_new() failed");
  161. goto quit;
  162. }
  163. if (pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)
  164. < 0) {
  165. warnx("pa_context_connect() failed: %s",
  166. pa_strerror(pa_context_errno(context)));
  167. goto quit;
  168. }
  169. pa_context_set_state_callback(context,
  170. context_state_callback,
  171. userdata);
  172. if (pa_mainloop_run(m, &ret) < 0) warnx("pa_mainloop_run() failed");
  173. quit:
  174. if (!!context) pa_context_unref(context);
  175. if (!!m) {
  176. pa_signal_done();
  177. pa_mainloop_free(m);
  178. }
  179. return (data->mainloop = NULL);
  180. }
  181. static inline void
  182. pulse_cleanup(void *ptr)
  183. {
  184. struct volume_static_data *data = ptr;
  185. pthread_cancel(data->volume_thread);
  186. if (!!data->mainloop) quit(0, data->mainloop);
  187. pthread_join(data->volume_thread, NULL);
  188. }