#include "e_mod_main.h" #include "e_mod_mixer.h" #include "Pulse.h" static Ecore_Exe *pulse_inst = NULL; static Eina_Bool pa_started = EINA_FALSE; static Pulse *conn = NULL; static Pulse_Server_Info *info = NULL; static Pulse_Sink *default_sink = NULL; static Eina_List *handlers = NULL; static Eina_List *sinks = NULL; static Eina_List *sources = NULL; static Eina_Hash *queue_states = NULL; static const char *_name = NULL; static Ecore_Timer *disc_timer = NULL; static unsigned int disc_count = 0; static unsigned int update_count = 0; static Ecore_Timer *update_timer = NULL; static Eina_Bool _pulse_start(void *d EINA_UNUSED) { update_timer = NULL; e_mixer_pulse_init(); return EINA_FALSE; } static Eina_Bool _pulse_started(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Exe_Event_Add *ev) { if (ev->exe != pulse_inst) return ECORE_CALLBACK_RENEW; if (!update_timer) update_timer = ecore_timer_add(2.0, _pulse_start, NULL); pa_started = EINA_TRUE; pulse_inst = NULL; E_FREE_LIST(handlers, ecore_event_handler_del); return ECORE_CALLBACK_DONE; } static Eina_Bool _pulse_not_started(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Exe_Event_Del *ev) { if (ev->exe != pulse_inst) return ECORE_CALLBACK_RENEW; if (!pa_started) { E_FREE_LIST(handlers, ecore_event_handler_del); e_mod_mixer_pulse_ready(EINA_FALSE); } return ECORE_CALLBACK_DONE; } static void _pulse_info_get(Pulse *d EINA_UNUSED, int type EINA_UNUSED, Pulse_Server_Info *ev) { Eina_List *l; Pulse_Sink *sink; pulse_server_info_free(info); info = ev; EINA_LIST_FOREACH(sinks, l, sink) if (ev->default_sink == pulse_sink_name_get(sink)) { if (default_sink == sink) return; default_sink = sink; if (!_mixer_using_default) e_mod_mixer_pulse_update(); break; } e_mod_mixer_pulse_ready(EINA_TRUE); } static Eina_Bool _pulse_update_timer(void *d EINA_UNUSED) { e_mod_mixer_pulse_update(); update_timer = NULL; return EINA_FALSE; } static Eina_Bool _pulse_update(Pulse *d EINA_UNUSED, int type EINA_UNUSED, Pulse_Sink *ev EINA_UNUSED) { Pulse_Tag_Id id; id = pulse_server_info_get(conn); if (id) pulse_cb_set(conn, id, (Pulse_Cb)_pulse_info_get); if (update_timer) ecore_timer_reset(update_timer); else update_timer = ecore_timer_add(0.2, _pulse_update_timer, NULL); return EINA_TRUE; } static void _pulse_sinks_get(Pulse *p EINA_UNUSED, Pulse_Tag_Id id EINA_UNUSED, Eina_List *ev) { Eina_List *l; Pulse_Sink *sink; E_FREE_LIST(sinks, pulse_sink_free); EINA_LIST_FOREACH(ev, l, sink) { /* printf("Sink:\n"); printf("\tname: %s\n", pulse_sink_name_get(sink)); printf("\tidx: %"PRIu32"\n", pulse_sink_idx_get(sink)); printf("\tdesc: %s\n", pulse_sink_desc_get(sink)); printf("\tchannels: %u\n", pulse_sink_channels_count(sink)); printf("\tmuted: %s\n", pulse_sink_muted_get(sink) ? "yes" : "no"); printf("\tavg: %g\n", pulse_sink_avg_get_pct(sink)); printf("\tbalance: %f\n", pulse_sink_balance_get(sink)); */ if (info && (!default_sink)) { if (info->default_sink == pulse_sink_name_get(sink)) { default_sink = sink; break; } } } sinks = ev; pulse_sinks_watch(conn); if (default_sink) e_mod_mixer_pulse_ready(EINA_TRUE); } static void _pulse_sources_get(Pulse *p EINA_UNUSED, Pulse_Tag_Id id EINA_UNUSED, Eina_List *ev) { eina_list_free(sources); sources = ev; /* Eina_List *l; Pulse_Sink *sink; sources = ev; EINA_LIST_FOREACH(ev, l, sink) { printf("Sources:\n"); printf("\tname: %s\n", pulse_sink_name_get(sink)); printf("\tidx: %"PRIu32"\n", pulse_sink_idx_get(sink)); printf("\tdesc: %s\n", pulse_sink_desc_get(sink)); printf("\tchannels: %u\n", pulse_sink_channels_count(sink)); printf("\tmuted: %s\n", pulse_sink_muted_get(sink) ? "yes" : "no"); printf("\tavg: %g\n", pulse_sink_avg_get_pct(sink)); printf("\tbalance: %f\n", pulse_sink_balance_get(sink)); } */ } static Eina_Bool _pulse_connected(Pulse *d, int type EINA_UNUSED, Pulse *ev) { Pulse_Tag_Id id; if (d != ev) return ECORE_CALLBACK_PASS_ON; id = pulse_sinks_get(conn); if (!id) { e_mixer_pulse_shutdown(); e_mixer_default_setup(); return ECORE_CALLBACK_RENEW; } if (!queue_states) queue_states = eina_hash_stringshared_new(free); pulse_cb_set(conn, id, (Pulse_Cb)_pulse_sinks_get); id = pulse_sources_get(conn); if (id) pulse_cb_set(conn, id, (Pulse_Cb)_pulse_sources_get); id = pulse_server_info_get(conn); if (id) pulse_cb_set(conn, id, (Pulse_Cb)_pulse_info_get); return ECORE_CALLBACK_RENEW; } static Eina_Bool _pulse_disc_timer(void *d EINA_UNUSED) { disc_timer = NULL; if (disc_count < 5) { if (pulse_connect(conn)) return EINA_FALSE; } e_mod_mixer_pulse_ready(EINA_FALSE); e_mixer_pulse_shutdown(); e_mixer_pulse_init(); disc_count = 0; return EINA_FALSE; } static Eina_Bool _pulse_disconnected(Pulse *d, int type EINA_UNUSED, Pulse *ev) { Pulse_Sink *sink; if (d != ev) return ECORE_CALLBACK_PASS_ON; EINA_LIST_FREE(sinks, sink) pulse_sink_free(sink); EINA_LIST_FREE(sources, sink) pulse_sink_free(sink); pulse_server_info_free(info); if (queue_states) eina_hash_free(queue_states); queue_states = NULL; info = NULL; default_sink = NULL; if (update_timer) ecore_timer_del(update_timer); update_timer = NULL; // printf("PULSEAUDIO: disconnected at %g\n", ecore_time_unix_get()); disc_count++; if (disc_timer) return ECORE_CALLBACK_RENEW; disc_timer = ecore_timer_add(1.5, _pulse_disc_timer, NULL); return ECORE_CALLBACK_RENEW; } static void _pulse_state_queue(Pulse_Sink *sink, int left, int right, int mute) { E_Mixer_Channel_State *state = NULL; if (queue_states) state = eina_hash_find(queue_states, pulse_sink_name_get(sink)); else queue_states = eina_hash_stringshared_new(free); if (!state) { state = E_NEW(E_Mixer_Channel_State, 1); eina_hash_direct_add(queue_states, pulse_sink_name_get(sink), state); state->left = state->right = state->mute = -1; } if (left >= 0) state->left = left; if (right >= 0) state->right = right; if (mute >= 0) state->mute = mute; } static Pulse_Sink * _pulse_sink_find(const char *name) { Eina_List *l; Pulse_Sink *sink; EINA_LIST_FOREACH(sinks, l, sink) { const char *sink_name; sink_name = pulse_sink_name_get(sink); if ((sink_name == name) || (!e_util_strcmp(sink_name, name))) return sink; } EINA_LIST_FOREACH(sources, l, sink) { const char *sink_name; sink_name = pulse_sink_name_get(sink); if ((sink_name == name) || (!e_util_strcmp(sink_name, name))) return sink; } return NULL; } static Eina_Bool _pulse_queue_process(const Eina_Hash *h EINA_UNUSED, const char *key, E_Mixer_Channel_State *state, void *d EINA_UNUSED) { Eina_List *l, *list[2] = {sinks, sources}; E_Mixer_Channel_Info ch; void *s; int x; if ((state->mute == -1) && (state->left == -1) && (state->right == -1)) return EINA_TRUE; ch.id = (void*)1; for (x = 0; x < 2; x++) EINA_LIST_FOREACH(list[x], l, s) { if (key != pulse_sink_name_get(s)) continue; if ((state->left >= 0) || (state->right >= 0)) e_mixer_pulse_set_volume(s, &ch, state->left, state->right); if (state->mute >= 0) e_mixer_pulse_set_mute(s, &ch, state->mute); state->left = state->right = state->mute = -1; return EINA_FALSE; } return EINA_TRUE; } static void _pulse_result_cb(Pulse *p EINA_UNUSED, Pulse_Tag_Id id, void *ev) { if (!ev) fprintf(stderr, "Command %u failed!\n", id); if (!update_count) return; if (--update_count) return; if (!queue_states) return; eina_hash_foreach(queue_states, (Eina_Hash_Foreach)_pulse_queue_process, NULL); } Eina_Bool e_mixer_pulse_ready(void) { return !!sinks; } Eina_Bool e_mixer_pulse_init(void) { pulse_init(); conn = pulse_new(); if ((!conn) || (!pulse_connect(conn))) { pulse_free(conn); conn = NULL; pulse_shutdown(); if (pa_started) { e_mod_mixer_pulse_ready(EINA_FALSE); return EINA_FALSE; } pulse_inst = ecore_exe_run("start-pulseaudio-x11", NULL); if (!pulse_inst) return EINA_FALSE; E_LIST_HANDLER_APPEND(handlers, ECORE_EXE_EVENT_ADD, (Ecore_Event_Handler_Cb)_pulse_started, NULL); E_LIST_HANDLER_APPEND(handlers, ECORE_EXE_EVENT_DEL, (Ecore_Event_Handler_Cb)_pulse_not_started, NULL); return EINA_TRUE; } E_LIST_HANDLER_APPEND(handlers, PULSE_EVENT_CONNECTED, (Ecore_Event_Handler_Cb)_pulse_connected, conn); E_LIST_HANDLER_APPEND(handlers, PULSE_EVENT_CHANGE, (Ecore_Event_Handler_Cb)_pulse_update, conn); E_LIST_HANDLER_APPEND(handlers, PULSE_EVENT_DISCONNECTED, (Ecore_Event_Handler_Cb)_pulse_disconnected, conn); if (!_name) _name = eina_stringshare_add("Output"); return EINA_TRUE; } void e_mixer_pulse_shutdown(void) { Pulse_Sink *sink; EINA_LIST_FREE(sinks, sink) pulse_sink_free(sink); EINA_LIST_FREE(sources, sink) pulse_sink_free(sink); pulse_server_info_free(info); info = NULL; default_sink = NULL; update_count = 0; if (update_timer) ecore_timer_del(update_timer); update_timer = NULL; pulse_free(conn); conn = NULL; E_FREE_LIST(handlers, ecore_event_handler_del); if (queue_states) eina_hash_free(queue_states); queue_states = NULL; pulse_shutdown(); if (_name) eina_stringshare_del(_name); _name = NULL; } E_Mixer_System * e_mixer_pulse_new(const char *name) { return (E_Mixer_System *)_pulse_sink_find(name); } void e_mixer_pulse_del(E_Mixer_System *self EINA_UNUSED) { } Eina_List * e_mixer_pulse_get_cards(void) { Eina_List *l, *ret = NULL; Pulse_Sink *sink; EINA_LIST_FOREACH(sinks, l, sink) ret = eina_list_append(ret, eina_stringshare_ref(pulse_sink_name_get(sink))); EINA_LIST_FOREACH(sources, l, sink) ret = eina_list_append(ret, eina_stringshare_ref(pulse_sink_name_get(sink))); return ret; } const char * e_mixer_pulse_get_default_card(void) { if (default_sink) return eina_stringshare_ref(pulse_sink_name_get(default_sink)); return NULL; } const char * e_mixer_pulse_get_card_name(const char *card) { Pulse_Sink *sink; const char *s; sink = _pulse_sink_find(card); s = pulse_sink_desc_get(sink); if ((!s) || (!s[0])) s = pulse_sink_name_get(sink); return eina_stringshare_ref(s); } Eina_List * e_mixer_pulse_get_channels(const E_Mixer_System *self EINA_UNUSED) { E_Mixer_Channel_Info *ch_info; ch_info = malloc(sizeof(*ch_info)); ch_info->id = (void*)1; ch_info->name = eina_stringshare_ref(_name); ch_info->capabilities= E_MIXER_CHANNEL_CAN_MUTE|E_MIXER_CHANNEL_HAS_PLAYBACK; return eina_list_append(NULL, ch_info); } Eina_List * e_mixer_pulse_get_channel_names(const E_Mixer_System *self EINA_UNUSED) { return eina_list_append(NULL, eina_stringshare_ref(_name)); } const char * e_mixer_pulse_get_default_channel_name(const E_Mixer_System *self EINA_UNUSED) { return eina_stringshare_ref(_name); } E_Mixer_Channel_Info * e_mixer_pulse_get_channel_by_name(const E_Mixer_System *self EINA_UNUSED, const char *name EINA_UNUSED) { E_Mixer_Channel_Info *ch_info; ch_info = malloc(sizeof(*ch_info)); ch_info->id = (void*)1; ch_info->name = eina_stringshare_ref(_name); ch_info->capabilities= E_MIXER_CHANNEL_CAN_MUTE|E_MIXER_CHANNEL_HAS_PLAYBACK; return ch_info; } int e_mixer_pulse_get_volume(const E_Mixer_System *self, const E_Mixer_Channel_Info *channel, int *left, int *right) { double volume; int x, n; if (!channel) return 0; n = pulse_sink_channels_count((void *)self); for (x = 0; x < n; x++) { volume = pulse_sink_channel_volume_get((void *)self, ((uintptr_t)x)); if (x == 0) { if (left) *left = (int)volume; } else if (x == 1) { if (right) *right = (int)volume; } } return 1; } int e_mixer_pulse_set_volume(const E_Mixer_System *self, const E_Mixer_Channel_Info *channel, int left, int right) { uint32_t id = 0; int x, n; if (!channel) return 0; if (update_count > 1) { _pulse_state_queue((void*)self, left, right, -1); return 1; } n = pulse_sink_channels_count((void *)self); for (x = 0; x < n; x++, id = 0) { double vol; vol = lround(pulse_sink_channel_volume_get((void *)self, x)); if (x == 0) { if (vol != left) id = pulse_sink_channel_volume_set(conn, (void *)self, x, left); } else if (x == 1) { if (vol != right) id = pulse_sink_channel_volume_set(conn, (void *)self, x, right); } if (id) { pulse_cb_set(conn, id, (Pulse_Cb)_pulse_result_cb); update_count++; } } return 1; } int e_mixer_pulse_get_mute(const E_Mixer_System *self, const E_Mixer_Channel_Info *channel EINA_UNUSED, int *mute) { if (mute) *mute = pulse_sink_muted_get((void *)self); return 1; } int e_mixer_pulse_set_mute(const E_Mixer_System *self, const E_Mixer_Channel_Info *channel EINA_UNUSED, int mute) { uint32_t id; Eina_Bool source = EINA_FALSE; if (update_count > 2) { _pulse_state_queue((void*)self, -1, -1, mute); return 1; } source = !!eina_list_data_find(sources, self); id = pulse_type_mute_set(conn, pulse_sink_idx_get((void *)self), mute, source); if (!id) return 0; update_count++; pulse_cb_set(conn, id, (Pulse_Cb)_pulse_result_cb); return 1; } int e_mixer_pulse_get_state(const E_Mixer_System *self, const E_Mixer_Channel_Info *channel, E_Mixer_Channel_State *state) { if (!state) return 0; if (!channel) return 0; e_mixer_pulse_get_mute(self, channel, &(state->mute)); e_mixer_pulse_get_volume(self, channel, &(state->left), &(state->right)); return 1; }