summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlavio Ceolin <flavio.ceolin@gmail.com>2013-09-01 19:41:13 -0300
committerFlavio Ceolin <flavio.ceolin@gmail.com>2014-04-03 16:23:04 -0300
commit196cdb70b84958dd827797f86e78abd798e48cf7 (patch)
treefa94c67c0794b8dd8d64b8e216daccc20801c584 /src
parentc984cf740b8625571a5d1617e0c3911fc27c11f9 (diff)
Adding common code
Init/shutdown used libraries and including common headers.
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.c31
-rw-r--r--src/bin/main_window.c160
-rw-r--r--src/bin/main_window.h12
-rw-r--r--src/bin/playbacks_view.c375
-rw-r--r--src/bin/playbacks_view.h12
-rw-r--r--src/bin/sinks_view.c320
-rw-r--r--src/bin/sinks_view.h13
-rw-r--r--src/bin/sources_view.c263
-rw-r--r--src/bin/sources_view.h13
-rw-r--r--src/lib/common.c64
-rw-r--r--src/lib/common.h31
-rw-r--r--src/lib/epulse.c781
-rw-r--r--src/lib/epulse.h63
-rw-r--r--src/lib/epulse_ml.c (renamed from src/epulse_ml.c)14
-rw-r--r--src/main.c18
-rw-r--r--src/module/e_mod_main.c586
-rw-r--r--src/module/e_mod_main.h12
17 files changed, 2743 insertions, 25 deletions
diff --git a/src/bin/main.c b/src/bin/main.c
new file mode 100644
index 0000000..66cd35a
--- /dev/null
+++ b/src/bin/main.c
@@ -0,0 +1,31 @@
1#include <common.h>
2#include <epulse.h>
3#include "main_window.h"
4
5#define DEFAULT_HEIGHT 600
6#define DEFAULT_WIDTH 800
7
8EAPI int
9elm_main(int argc EINA_UNUSED, char *argv[] EINA_UNUSED)
10{
11 Evas_Object *win;
12
13 EINA_SAFETY_ON_FALSE_RETURN_VAL(epulse_common_init("epulse"), EXIT_FAILURE);
14 EINA_SAFETY_ON_FALSE_RETURN_VAL(epulse_init() > 0, EXIT_FAILURE);
15
16 win = main_window_add();
17 evas_object_resize(win, DEFAULT_WIDTH, DEFAULT_HEIGHT);
18 evas_object_show(win);
19
20 elm_run();
21
22 epulse_common_shutdown();
23 epulse_shutdown();
24 return 0;
25}
26
27/*
28 * Create the default main() that will work with both quicklaunch or
29 * regular applications.
30 */
31ELM_MAIN()
diff --git a/src/bin/main_window.c b/src/bin/main_window.c
new file mode 100644
index 0000000..6783384
--- /dev/null
+++ b/src/bin/main_window.c
@@ -0,0 +1,160 @@
1#include "main_window.h"
2
3#include "epulse.h"
4#include "playbacks_view.h"
5#include "sinks_view.h"
6#include "sources_view.h"
7
8#define MAIN_WINDOW_DATA "mainwindow.data"
9
10enum MAIN_SUBVIEWS {
11 PLAYBACKS,
12 OUTPUTS,
13 INPUTS
14};
15
16static const char *_views[] = {
17 "Playback",
18 "Output Devices",
19 "Input Devices"
20};
21
22typedef struct _Main_Window Main_Window;
23struct _Main_Window
24{
25 Evas_Object *win;
26 Evas_Object *toolbar;
27 Evas_Object *layout;
28 Evas_Object *naviframe;
29 Evas_Object *playbacks;
30 Evas_Object *inputs;
31 Evas_Object *outputs;
32 Elm_Object_Item *toolbar_items[3];
33 Elm_Object_Item *views[3];
34};
35
36static void
37_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED,
38 void *event_info EINA_UNUSED)
39{
40 Main_Window *mw = data;
41
42 free(mw);
43}
44
45static void
46_delete_request_cb(void *data EINA_UNUSED, Evas_Object *o EINA_UNUSED,
47 void *event_info EINA_UNUSED)
48{
49 elm_exit();
50}
51
52static void
53_toolbar_item_cb(void *data, Evas_Object *obj EINA_UNUSED,
54 void *event_info EINA_UNUSED)
55{
56 Main_Window *mw = data;
57 Elm_Object_Item *it = elm_toolbar_selected_item_get(obj);
58
59 if (!mw->views[PLAYBACKS] || !mw->views[OUTPUTS] ||
60 !mw->views[INPUTS])
61 return;
62
63 if (it == mw->toolbar_items[PLAYBACKS])
64 elm_naviframe_item_promote(mw->views[PLAYBACKS]);
65 else if (it == mw->toolbar_items[OUTPUTS])
66 elm_naviframe_item_promote(mw->views[OUTPUTS]);
67 else
68 elm_naviframe_item_promote(mw->views[INPUTS]);
69}
70
71Evas_Object *
72main_window_add(void)
73{
74 Main_Window *mw;
75 Evas_Object *tmp, *box;
76 unsigned int i;
77
78 elm_theme_extension_add(NULL, EPULSE_THEME);
79 mw = calloc(1, sizeof(Main_Window));
80 if (!mw)
81 {
82 ERR("Could not allocate memmory to main window");
83 return NULL;
84 }
85
86 tmp = elm_win_add(NULL, PACKAGE_NAME, ELM_WIN_BASIC);
87 EINA_SAFETY_ON_NULL_GOTO(tmp, win_err);
88 evas_object_data_set(tmp, MAIN_WINDOW_DATA, mw);
89 evas_object_event_callback_add(tmp, EVAS_CALLBACK_DEL, _del_cb, mw);
90 elm_win_autodel_set(tmp, EINA_TRUE);
91 elm_win_title_set(tmp, "Efl Volume Control");
92 mw->win = tmp;
93
94 tmp = elm_bg_add(mw->win);
95 evas_object_size_hint_weight_set(tmp, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
96 evas_object_size_hint_align_set(tmp, EVAS_HINT_FILL, EVAS_HINT_FILL);
97 elm_win_resize_object_add(mw->win, tmp);
98 evas_object_show(tmp);
99
100 box = elm_box_add(mw->win);
101 evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
102 elm_box_horizontal_set(box, EINA_FALSE);
103 elm_win_resize_object_add(mw->win, box);
104 evas_object_show(box);
105
106 tmp = elm_toolbar_add(mw->win);
107 evas_object_size_hint_weight_set(tmp, EVAS_HINT_EXPAND, 0);
108 evas_object_size_hint_align_set(tmp, EVAS_HINT_FILL, EVAS_HINT_FILL);
109 elm_toolbar_select_mode_set(tmp, ELM_OBJECT_SELECT_MODE_ALWAYS);
110 elm_box_pack_start(box, tmp);
111 mw->toolbar = tmp;
112 evas_object_show(tmp);
113
114 tmp = elm_naviframe_add(mw->win);
115 elm_object_style_set(tmp, "no_transition");
116 elm_naviframe_prev_btn_auto_pushed_set(tmp, EINA_FALSE);
117 evas_object_size_hint_weight_set(tmp, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
118 evas_object_size_hint_align_set(tmp, EVAS_HINT_FILL, EVAS_HINT_FILL);
119 elm_box_pack_end(box, tmp);
120 evas_object_show(tmp);
121 mw->naviframe = tmp;
122
123 for (i = 0; i < EINA_C_ARRAY_LENGTH(_views); i++)
124 {
125 mw->toolbar_items[i] = elm_toolbar_item_append(mw->toolbar, NULL,
126 _views[i],
127 _toolbar_item_cb, mw);
128 }
129
130 evas_object_smart_callback_add(mw->win, "delete,request",
131 _delete_request_cb, mw);
132
133 /* Creating the playbacks view */
134 mw->playbacks = playbacks_view_add(mw->win);
135 evas_object_size_hint_weight_set(mw->playbacks, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
136 evas_object_size_hint_align_set(mw->playbacks, EVAS_HINT_FILL, EVAS_HINT_FILL);
137 evas_object_show(mw->playbacks);
138
139 /* Creating the outputs view */
140 mw->outputs = sinks_view_add(mw->win);
141 evas_object_size_hint_weight_set(mw->outputs, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
142 evas_object_size_hint_align_set(mw->outputs, EVAS_HINT_FILL, EVAS_HINT_FILL);
143 evas_object_show(mw->outputs);
144
145 /* Creating the inputs view */
146 mw->inputs = sources_view_add(mw->win);
147 evas_object_size_hint_weight_set(mw->inputs, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
148 evas_object_size_hint_align_set(mw->inputs, EVAS_HINT_FILL, EVAS_HINT_FILL);
149 evas_object_show(mw->inputs);
150
151 mw->views[INPUTS] = elm_naviframe_item_simple_push(mw->naviframe, mw->inputs);
152 mw->views[OUTPUTS] = elm_naviframe_item_simple_push(mw->naviframe, mw->outputs);
153 mw->views[PLAYBACKS] = elm_naviframe_item_simple_push(mw->naviframe, mw->playbacks);
154
155 return mw->win;
156
157 win_err:
158 free(mw);
159 return NULL;
160}
diff --git a/src/bin/main_window.h b/src/bin/main_window.h
new file mode 100644
index 0000000..dd63ae7
--- /dev/null
+++ b/src/bin/main_window.h
@@ -0,0 +1,12 @@
1#ifndef _MAIN_WINDOW_H_
2#define _MAIN_WINDOW_H_
3
4#include <common.h>
5
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
9
10Evas_Object *main_window_add(void);
11
12#endif /* _MAIN_WINDOW_H_ */
diff --git a/src/bin/playbacks_view.c b/src/bin/playbacks_view.c
new file mode 100644
index 0000000..98edff4
--- /dev/null
+++ b/src/bin/playbacks_view.c
@@ -0,0 +1,375 @@
1#include "playbacks_view.h"
2
3#include "epulse.h"
4
5#define PLAYBACKS_KEY "playbacks.key"
6
7struct Sink
8{
9 int index;
10 const char *name;
11};
12
13struct Playbacks_View
14{
15 Evas_Object *self;
16 Evas_Object *genlist;
17 Elm_Genlist_Item_Class *itc;
18
19 Eina_List *inputs;
20 Eina_List *sinks;
21
22 Ecore_Event_Handler *sink_input_added;
23 Ecore_Event_Handler *sink_input_changed;
24 Ecore_Event_Handler *sink_input_removed;
25
26 Ecore_Event_Handler *sink_added;
27 Ecore_Event_Handler *sink_removed;
28};
29
30struct Sink_Input
31{
32 struct Playbacks_View *pv;
33
34 int index;
35 int sink_index;
36 pa_cvolume volume;
37 const char *name;
38 const char *icon;
39 Eina_Bool mute;
40
41 Elm_Object_Item *item;
42};
43
44static Eina_Bool
45_sink_input_add_cb(void *data, int type EINA_UNUSED,
46 void *info)
47{
48 struct Playbacks_View *pv = data;
49 Epulse_Event_Sink_Input *ev = info;
50 struct Sink_Input *input = calloc(1, sizeof(struct Sink_Input));
51 EINA_SAFETY_ON_NULL_RETURN_VAL(input, ECORE_CALLBACK_PASS_ON);
52
53 input->name = eina_stringshare_add(ev->base.name);
54 input->icon = eina_stringshare_add(ev->icon);
55 input->index = ev->base.index;
56 input->sink_index = ev->sink;
57 input->volume = ev->base.volume;
58 input->mute = ev->base.mute;
59 input->pv = pv;
60
61 pv->inputs = eina_list_append(pv->inputs, input);
62 input->item = elm_genlist_item_append(pv->genlist, pv->itc, input, NULL,
63 ELM_GENLIST_ITEM_NONE, NULL, pv);
64
65 return ECORE_CALLBACK_DONE;
66}
67
68static Eina_Bool
69_sink_input_removed_cb(void *data, int type EINA_UNUSED,
70 void *info)
71{
72 struct Playbacks_View *pv = data;
73 Epulse_Event_Sink_Input *ev = info;
74 Eina_List *l, *ll;
75 struct Sink_Input *input;
76
77 EINA_LIST_FOREACH_SAFE(pv->inputs, l, ll, input)
78 {
79 if (input->index == ev->base.index)
80 {
81 pv->inputs = eina_list_remove_list(pv->inputs, l);
82 elm_object_item_del(input->item);
83 break;
84 }
85 }
86
87 return ECORE_CALLBACK_DONE;
88}
89
90static Eina_Bool
91_sink_input_changed_cb(void *data, int type EINA_UNUSED,
92 void *info)
93{
94 struct Playbacks_View *pv = data;
95 Epulse_Event_Sink_Input *ev = info;
96 Eina_List *l;
97 struct Sink_Input *input;
98 Evas_Object *item = NULL;
99
100 EINA_LIST_FOREACH(pv->inputs, l, input)
101 {
102 if (input->index == ev->base.index)
103 {
104 pa_volume_t vol = pa_cvolume_avg(&ev->base.volume);
105
106 item = elm_object_item_part_content_get(input->item, "item");
107 input->volume = ev->base.volume;
108 if (item)
109 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
110
111 item = elm_object_item_part_content_get(input->item, "mute");
112 input->mute = ev->base.mute;
113 if (item)
114 {
115 elm_check_state_set(item, input->mute);
116 }
117 break;
118 }
119 }
120
121 return ECORE_CALLBACK_DONE;
122}
123
124static Eina_Bool
125_sink_add_cb(void *data, int type EINA_UNUSED,
126 void *info)
127{
128 struct Playbacks_View *pv = data;
129 Epulse_Event_Sink *ev = info;
130 struct Sink *sink = calloc(1, sizeof(struct Sink));
131 EINA_SAFETY_ON_NULL_RETURN_VAL(sink, ECORE_CALLBACK_PASS_ON);
132
133 sink->name = eina_stringshare_add(ev->base.name);
134 sink->index = ev->base.index;
135
136 pv->sinks = eina_list_append(pv->sinks, sink);
137 elm_genlist_realized_items_update(pv->genlist);
138
139 return ECORE_CALLBACK_PASS_ON;
140}
141
142static Eina_Bool
143_sink_removed_cb(void *data, int type EINA_UNUSED,
144 void *info)
145{
146 struct Playbacks_View *pv = data;
147 Epulse_Event_Sink *ev = info;
148 Eina_List *l, *ll;
149 struct Sink *sink;
150
151 EINA_LIST_FOREACH_SAFE(pv->sinks, l, ll, sink)
152 {
153 if (sink->index == ev->base.index)
154 {
155 pv->sinks = eina_list_remove_list(pv->sinks, l);
156 break;
157 }
158 }
159
160 elm_genlist_realized_items_update(pv->genlist);
161 return ECORE_CALLBACK_PASS_ON;
162}
163
164static void
165_del_cb(void *data,
166 Evas *e EINA_UNUSED,
167 Evas_Object *o EINA_UNUSED,
168 void *event_info EINA_UNUSED)
169{
170 struct Playbacks_View *pv = data;
171 struct Sink *sink;
172 eina_list_free(pv->inputs);
173
174 EINA_LIST_FREE(pv->sinks, sink)
175 {
176 eina_stringshare_del(sink->name);
177 free(sink);
178 }
179
180#define ECORE_EVENT_HANDLER_DEL(_handle) \
181 if (pv->_handle) \
182 { \
183 ecore_event_handler_del(pv->_handle); \
184 pv->_handle = NULL; \
185 }
186
187 ECORE_EVENT_HANDLER_DEL(sink_input_added)
188 ECORE_EVENT_HANDLER_DEL(sink_input_changed)
189 ECORE_EVENT_HANDLER_DEL(sink_input_removed)
190 ECORE_EVENT_HANDLER_DEL(sink_added)
191 ECORE_EVENT_HANDLER_DEL(sink_removed)
192#undef ECORE_EVENT_HANDLER_DEL
193}
194
195static char *
196_item_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part)
197{
198 struct Sink_Input *input = data;
199
200 if (!strcmp(part, "name"))
201 {
202 return strdup(input->name);
203 }
204
205 return NULL;
206}
207
208static void
209_item_del(void *data, Evas_Object *obj EINA_UNUSED)
210{
211 struct Sink_Input *input = data;
212
213 eina_stringshare_del(input->name);
214 eina_stringshare_del(input->icon);
215 free(input);
216}
217
218static void
219_volume_changed_cb(void *data, Evas_Object *o,
220 void *event_info EINA_UNUSED)
221{
222 struct Sink_Input *input = data;
223 double val = elm_slider_value_get(o);
224 pa_volume_t v = INT_TO_PA_VOLUME(val);
225
226 pa_cvolume_set(&input->volume, input->volume.channels, v);
227
228 epulse_sink_input_volume_set(input->index, input->volume);
229}
230
231static void
232_mute_changed_cb(void *data, Evas_Object *o EINA_UNUSED,
233 void *event_info EINA_UNUSED)
234{
235 struct Sink_Input *input = data;
236
237 if (!epulse_sink_input_mute_set(input->index, input->mute))
238 {
239 ERR("Could not mute the input: %d", input->index);
240 input->mute = !input->mute;
241 return;
242 }
243}
244
245static void
246_sink_selected(void *data, Evas_Object *obj, void *event_info)
247{
248 struct Sink_Input *input = data;
249 Elm_Object_Item *item = event_info;
250 struct Sink *sink = elm_object_item_data_get(item);
251
252 epulse_sink_input_move(input->index, sink->index);
253 elm_object_text_set(obj, sink->name);
254}
255
256static Evas_Object *
257_item_content_get(void *data, Evas_Object *obj, const char *part)
258{
259 Evas_Object *item = NULL;
260 struct Sink_Input *input = data;
261
262 if (!strcmp(part, "slider"))
263 {
264 pa_volume_t vol = pa_cvolume_avg(&input->volume);
265 item = elm_slider_add(obj);
266 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
267
268 elm_slider_unit_format_set(item, "%1.0f");
269 elm_slider_indicator_format_set(item, "%1.0f");
270 elm_slider_span_size_set(item, 120);
271 elm_slider_min_max_set(item, 0.0, 100.0);
272 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
273
274 evas_object_smart_callback_add(item, "delay,changed",
275 _volume_changed_cb, input);
276 }
277 else if (!strcmp(part, "mute"))
278 {
279 item = elm_check_add(obj);
280 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
281
282 elm_object_style_set(item, "toggle");
283 elm_object_part_text_set(item, "off", "Mute");
284 elm_object_part_text_set(item, "on", "Unmute");
285
286 elm_check_state_set(item, input->mute);
287 elm_check_state_pointer_set(item, &input->mute);
288 evas_object_smart_callback_add(item, "changed", _mute_changed_cb,
289 input);
290 }
291 else if (!strcmp(part, "icon"))
292 {
293 EINA_SAFETY_ON_NULL_RETURN_VAL(input->icon, NULL);
294
295 item = elm_icon_add(obj);
296 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
297
298 elm_icon_standard_set(item, input->icon);
299 }
300 else if (!strcmp(part, "hover"))
301 {
302 Eina_List *l;
303 struct Sink *sink;
304 item = elm_hoversel_add(obj);
305
306 EINA_LIST_FOREACH(input->pv->sinks, l, sink)
307 {
308 if (sink->index == input->sink_index)
309 elm_object_text_set(item, sink->name);
310 elm_hoversel_item_add(item, sink->name, NULL,
311 ELM_ICON_NONE, NULL, sink);
312 }
313 evas_object_smart_callback_add(item, "selected",
314 _sink_selected, input);
315 }
316
317 return item;
318}
319
320Evas_Object *
321playbacks_view_add(Evas_Object *parent)
322{
323 Evas_Object *layout;
324 struct Playbacks_View *pv;
325
326 pv = calloc(1, sizeof(struct Playbacks_View));
327 EINA_SAFETY_ON_NULL_RETURN_VAL(pv, NULL);
328
329 layout = epulse_layout_add(parent, "playbacks", "default");
330 EINA_SAFETY_ON_NULL_GOTO(layout, err);
331
332 evas_object_event_callback_add(layout, EVAS_CALLBACK_DEL, _del_cb, pv);
333
334 pv->genlist = elm_genlist_add(layout);
335 EINA_SAFETY_ON_NULL_GOTO(pv->genlist, err_genlist);
336
337 pv->sink_input_added = ecore_event_handler_add(SINK_INPUT_ADDED,
338 _sink_input_add_cb, pv);
339 pv->sink_input_added = ecore_event_handler_add(SINK_INPUT_CHANGED,
340 _sink_input_changed_cb, pv);
341 pv->sink_input_removed = ecore_event_handler_add(SINK_INPUT_REMOVED,
342 _sink_input_removed_cb, pv);
343 pv->sink_added = ecore_event_handler_add(SINK_ADDED,
344 _sink_add_cb, pv);
345 pv->sink_removed = ecore_event_handler_add(SINK_REMOVED,
346 _sink_removed_cb, pv);
347
348 pv->itc = elm_genlist_item_class_new();
349 EINA_SAFETY_ON_NULL_GOTO(pv->itc, err_genlist);
350 pv->itc->item_style = "playbacks";
351 pv->itc->func.text_get = _item_text_get;
352 pv->itc->func.content_get = _item_content_get;
353 pv->itc->func.del = _item_del;
354
355 evas_object_data_set(layout, PLAYBACKS_KEY, pv);
356 elm_layout_content_set(layout, "list", pv->genlist);
357
358 evas_object_size_hint_weight_set(pv->genlist, EVAS_HINT_EXPAND,
359 EVAS_HINT_EXPAND);
360 evas_object_size_hint_align_set(pv->genlist, EVAS_HINT_FILL,
361 EVAS_HINT_FILL);
362
363 return layout;
364
365 err_genlist:
366 ecore_event_handler_del(pv->sink_input_added);
367 ecore_event_handler_del(pv->sink_input_changed);
368 ecore_event_handler_del(pv->sink_input_removed);
369 ecore_event_handler_del(pv->sink_added);
370 ecore_event_handler_del(pv->sink_removed);
371 free(layout);
372 err:
373 free(pv);
374 return NULL;
375}
diff --git a/src/bin/playbacks_view.h b/src/bin/playbacks_view.h
new file mode 100644
index 0000000..a58d327
--- /dev/null
+++ b/src/bin/playbacks_view.h
@@ -0,0 +1,12 @@
1#ifndef _PLAYBACKS_VIEW_H_
2#define _PLAYBACKS_VIEW_H_
3
4#include "common.h"
5
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
9
10Evas_Object *playbacks_view_add(Evas_Object *parent);
11
12#endif /* _PLAYBACKS_VIEW_H_ */
diff --git a/src/bin/sinks_view.c b/src/bin/sinks_view.c
new file mode 100644
index 0000000..7ddea2c
--- /dev/null
+++ b/src/bin/sinks_view.c
@@ -0,0 +1,320 @@
1#include "sinks_view.h"
2
3#include "epulse.h"
4
5#define SINKS_KEY "sinks.key"
6
7struct Sink
8{
9 int index;
10 pa_cvolume volume;
11 const char *name;
12 Eina_Bool mute;
13
14 Elm_Object_Item *item;
15 Eina_List *ports;
16};
17
18struct Sink_Port
19{
20 char *name;
21 Eina_Bool active;
22};
23
24struct Sinks_View
25{
26 Evas_Object *self;
27 Evas_Object *genlist;
28 Elm_Genlist_Item_Class *itc;
29
30 Eina_List *sinks;
31 Ecore_Event_Handler *sink_added;
32 Ecore_Event_Handler *sink_changed;
33 Ecore_Event_Handler *sink_removed;
34};
35
36
37static Eina_Bool
38_sink_add_cb(void *data, int type EINA_UNUSED, void *info)
39{
40 struct Sinks_View *sv = data;
41 struct Sink_Port *sp;
42 Epulse_Event_Sink *ev = info;
43 Port *port;
44 Eina_List *l;
45 struct Sink *sink = calloc(1, sizeof(struct Sink));
46 EINA_SAFETY_ON_NULL_RETURN_VAL(sink, ECORE_CALLBACK_PASS_ON);
47
48 sink->name = eina_stringshare_add(ev->base.name);
49 sink->index = ev->base.index;
50 sink->volume = ev->base.volume;
51 sink->mute = ev->base.mute;
52
53 EINA_LIST_FOREACH(ev->ports, l, port)
54 {
55 sp = calloc(1, sizeof(struct Sink_Port));
56 sp->name = strdup(port->name);
57 if (port->active)
58 sp->active = EINA_TRUE;
59 sink->ports = eina_list_append(sink->ports, sp);
60 }
61
62 sv->sinks = eina_list_append(sv->sinks, sink);
63 sink->item = elm_genlist_item_append(sv->genlist, sv->itc, sink, NULL,
64 ELM_GENLIST_ITEM_NONE, NULL, sv);
65
66 return ECORE_CALLBACK_PASS_ON;
67}
68
69static Eina_Bool
70_sink_removed_cb(void *data, int type EINA_UNUSED, void *info)
71{
72 struct Sinks_View *sv = data;
73 Epulse_Event_Sink *ev = info;
74 Eina_List *l, *ll;
75 struct Sink *sink;
76
77 EINA_LIST_FOREACH_SAFE(sv->sinks, l, ll, sink)
78 {
79 if (sink->index == ev->base.index)
80 {
81 sv->sinks = eina_list_remove_list(sv->sinks, l);
82 elm_object_item_del(sink->item);
83 break;
84 }
85 }
86
87 return ECORE_CALLBACK_PASS_ON;
88}
89
90static Eina_Bool
91_sink_changed_cb(void *data, int type EINA_UNUSED, void *info)
92{
93 struct Sinks_View *sv = data;
94 Epulse_Event_Sink *ev = info;
95 Eina_List *l;
96 struct Sink *sink;
97 Evas_Object *item = NULL;
98
99 EINA_LIST_FOREACH(sv->sinks, l, sink)
100 {
101 if (sink->index == ev->base.index)
102 {
103 pa_volume_t vol = pa_cvolume_avg(&ev->base.volume);
104
105 item = elm_object_item_part_content_get(sink->item, "slider");
106 sink->volume = ev->base.volume;
107 if (item)
108 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
109
110 item = elm_object_item_part_content_get(sink->item, "mute");
111 sink->mute = ev->base.mute;
112 if (item)
113 elm_check_state_set(item, sink->mute);
114
115 break;
116 }
117 }
118
119 return ECORE_CALLBACK_DONE;
120}
121
122static void
123_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED,
124 void *event_info EINA_UNUSED)
125{
126 struct Sinks_View *sv = data;
127
128 eina_list_free(sv->sinks);
129 if (sv->sink_added)
130 {
131 ecore_event_handler_del(sv->sink_added);
132 sv->sink_added = NULL;
133 }
134 if (sv->sink_changed)
135 {
136 ecore_event_handler_del(sv->sink_changed);
137 sv->sink_changed = NULL;
138 }
139 if (sv->sink_removed)
140 {
141 ecore_event_handler_del(sv->sink_removed);
142 sv->sink_removed = NULL;
143 }
144}
145
146static char *
147_item_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part)
148{
149 struct Sink *sink = data;
150
151 if (!strcmp(part, "name"))
152 {
153 return strdup(sink->name);
154 }
155
156 return NULL;
157}
158
159static void
160_item_del(void *data, Evas_Object *obj EINA_UNUSED)
161{
162 struct Sink *sink = data;
163 struct Sink_Port *port;
164
165 eina_stringshare_del(sink->name);
166 EINA_LIST_FREE(sink->ports, port)
167 {
168 free(port->name);
169 free(port);
170 }
171 free(sink);
172}
173
174static void
175_volume_changed_cb(void *data, Evas_Object *o,
176 void *event_info EINA_UNUSED)
177{
178 struct Sink *sink = data;
179 double val = elm_slider_value_get(o);
180 pa_volume_t v = INT_TO_PA_VOLUME(val);
181
182 pa_cvolume_set(&sink->volume, sink->volume.channels, v);
183
184 epulse_sink_volume_set(sink->index, sink->volume);
185}
186
187static void
188_mute_changed_cb(void *data, Evas_Object *o EINA_UNUSED,
189 void *event_info EINA_UNUSED)
190{
191 struct Sink *sink = data;
192
193 if (!epulse_sink_mute_set(sink->index, sink->mute))
194 {
195 ERR("Could not mute the sink: %d", sink->index);
196 sink->mute = !sink->mute;
197 return;
198 }
199}
200
201static void
202_port_selected_cb(void *data, Evas_Object *o,
203 void *event_info EINA_UNUSED)
204{
205 struct Sink *sink = data;
206 Elm_Object_Item *item = event_info;
207 struct Sink_Port *port = elm_object_item_data_get(item);
208
209 if (!epulse_sink_port_set(sink->index, port->name))
210 ERR("Could not change the port");
211 else
212 elm_object_text_set(o, port->name);
213}
214
215static Evas_Object *
216_item_content_get(void *data, Evas_Object *obj, const char *part)
217{
218 Evas_Object *item = NULL;
219 struct Sink *sink = data;
220
221 if (!strcmp(part, "slider"))
222 {
223 pa_volume_t vol = pa_cvolume_avg(&sink->volume);
224 item = elm_slider_add(obj);
225 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
226
227 elm_slider_unit_format_set(item, "%1.0f");
228 elm_slider_indicator_format_set(item, "%1.0f");
229 elm_slider_span_size_set(item, 120);
230 elm_slider_min_max_set(item, 0.0, 100.0);
231 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
232 evas_object_smart_callback_add(item, "delay,changed",
233 _volume_changed_cb, sink);
234 }
235 else if (!strcmp(part, "mute"))
236 {
237 item = elm_check_add(obj);
238 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
239
240 elm_object_style_set(item, "toggle");
241 elm_object_part_text_set(item, "off", "Mute");
242 elm_object_part_text_set(item, "on", "Unmute");
243
244 elm_check_state_set(item, sink->mute);
245 elm_check_state_pointer_set(item, &sink->mute);
246 evas_object_smart_callback_add(item, "changed", _mute_changed_cb,
247 sink);
248 }
249 else if (!strcmp(part, "hover"))
250 {
251 Eina_List *l;
252 struct Sink_Port *sp;
253
254 if (sink->ports)
255 item = elm_hoversel_add(obj);
256
257 EINA_LIST_FOREACH(sink->ports, l, sp)
258 {
259 elm_hoversel_item_add(item, sp->name, NULL,
260 ELM_ICON_NONE, NULL, sp);
261 if (sp->active)
262 elm_object_text_set(item, sp->name);
263 }
264 evas_object_smart_callback_add(item, "selected",
265 _port_selected_cb, sink);
266 }
267
268 return item;
269}
270
271
272Evas_Object *
273sinks_view_add(Evas_Object *parent)
274{
275 Evas_Object *layout;
276 struct Sinks_View *sv;
277
278 sv = calloc(1, sizeof(struct Sinks_View));
279 EINA_SAFETY_ON_NULL_RETURN_VAL(sv, NULL);
280
281 layout = epulse_layout_add(parent, "sinks", "default");
282 EINA_SAFETY_ON_NULL_GOTO(layout, err);
283
284 evas_object_event_callback_add(layout, EVAS_CALLBACK_DEL, _del_cb, sv);
285
286 sv->genlist = elm_genlist_add(layout);
287 EINA_SAFETY_ON_NULL_GOTO(sv->genlist, err_genlist);
288
289 sv->sink_added = ecore_event_handler_add(SINK_ADDED, _sink_add_cb, sv);
290 sv->sink_added = ecore_event_handler_add(SINK_CHANGED, _sink_changed_cb, sv);
291 sv->sink_removed = ecore_event_handler_add(SINK_REMOVED,
292 _sink_removed_cb, sv);
293
294 sv->itc = elm_genlist_item_class_new();
295 EINA_SAFETY_ON_NULL_GOTO(sv->itc, err_genlist);
296 sv->itc->item_style = "sinks";
297 sv->itc->func.text_get = _item_text_get;
298 sv->itc->func.content_get = _item_content_get;
299 sv->itc->func.del = _item_del;
300
301 evas_object_data_set(layout, SINKS_KEY, sv);
302 elm_layout_content_set(layout, "list", sv->genlist);
303
304 evas_object_size_hint_weight_set(sv->genlist, EVAS_HINT_EXPAND,
305 EVAS_HINT_EXPAND);
306 evas_object_size_hint_align_set(sv->genlist, EVAS_HINT_FILL,
307 EVAS_HINT_FILL);
308
309 return layout;
310
311 err_genlist:
312 ecore_event_handler_del(sv->sink_added);
313 ecore_event_handler_del(sv->sink_changed);
314 ecore_event_handler_del(sv->sink_removed);
315 free(layout);
316 err:
317 free(sv);
318
319 return NULL;
320}
diff --git a/src/bin/sinks_view.h b/src/bin/sinks_view.h
new file mode 100644
index 0000000..f222234
--- /dev/null
+++ b/src/bin/sinks_view.h
@@ -0,0 +1,13 @@
1#ifndef _SINKS_VIEW_H_
2#define _SINKS_VIEW_H_
3
4#include "common.h"
5
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
9
10Evas_Object *sinks_view_add(Evas_Object *parent);
11
12
13#endif /* _SINKS_VIEW_H_ */
diff --git a/src/bin/sources_view.c b/src/bin/sources_view.c
new file mode 100644
index 0000000..aa6dafb
--- /dev/null
+++ b/src/bin/sources_view.c
@@ -0,0 +1,263 @@
1#include "sources_view.h"
2
3#include "epulse.h"
4
5#define SOURCES_KEY "sources.key"
6
7struct Source
8{
9 int index;
10 pa_cvolume volume;
11 const char *name;
12 Eina_Bool mute;
13
14 Elm_Object_Item *item;
15};
16
17struct Sources_View
18{
19 Evas_Object *self;
20 Evas_Object *genlist;
21 Elm_Genlist_Item_Class *itc;
22
23 Eina_List *sources;
24 Ecore_Event_Handler *source_added;
25 Ecore_Event_Handler *source_changed;
26 Ecore_Event_Handler *source_removed;
27};
28
29static Eina_Bool
30_source_add_cb(void *data, int type EINA_UNUSED, void *info)
31{
32 struct Sources_View *sv = data;
33 Epulse_Event *ev = info;
34 struct Source *source = calloc(1, sizeof(struct Source));
35 EINA_SAFETY_ON_NULL_RETURN_VAL(source, ECORE_CALLBACK_PASS_ON);
36
37 source->name = eina_stringshare_add(ev->name);
38 source->index = ev->index;
39 source->volume = ev->volume;
40 source->mute = ev->mute;
41
42 sv->sources = eina_list_append(sv->sources, source);
43 source->item = elm_genlist_item_append(sv->genlist, sv->itc, source, NULL,
44 ELM_GENLIST_ITEM_NONE, NULL, sv);
45
46 return ECORE_CALLBACK_DONE;
47}
48
49static Eina_Bool
50_source_removed_cb(void *data, int type EINA_UNUSED, void *info)
51{
52 struct Sources_View *sv = data;
53 Epulse_Event *ev = info;
54 Eina_List *l, *ll;
55 struct Source *source;
56
57 EINA_LIST_FOREACH_SAFE(sv->sources, l, ll, source)
58 {
59 if (source->index == ev->index)
60 {
61 sv->sources = eina_list_remove_list(sv->sources, l);
62 elm_object_item_del(source->item);
63 break;
64 }
65 }
66
67 return ECORE_CALLBACK_DONE;
68}
69
70static Eina_Bool
71_source_changed_cb(void *data, int type EINA_UNUSED, void *info)
72{
73 struct Sources_View *sv = data;
74 Epulse_Event *ev = info;
75 Eina_List *l;
76 struct Source *source;
77 Evas_Object *item = NULL;
78
79 EINA_LIST_FOREACH(sv->sources, l, source)
80 {
81 if (source->index == ev->index)
82 {
83 pa_volume_t vol = pa_cvolume_avg(&ev->volume);
84
85 item = elm_object_item_part_content_get(source->item, "slider");
86 source->volume = ev->volume;
87 if (item)
88 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
89
90 item = elm_object_item_part_content_get(source->item, "mute");
91 source->mute = ev->mute;
92 if (item)
93 {
94 elm_check_state_set(item, source->mute);
95 }
96 break;
97 }
98 }
99
100 return ECORE_CALLBACK_DONE;
101}
102
103static void
104_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED,
105 void *event_info EINA_UNUSED)
106{
107 struct Sources_View *sv = data;
108
109 eina_list_free(sv->sources);
110 if (sv->source_added)
111 {
112 ecore_event_handler_del(sv->source_added);
113 sv->source_added = NULL;
114 }
115 if (sv->source_changed)
116 {
117 ecore_event_handler_del(sv->source_changed);
118 sv->source_changed = NULL;
119 }
120 if (sv->source_removed)
121 {
122 ecore_event_handler_del(sv->source_removed);
123 sv->source_removed = NULL;
124 }
125}
126
127static char *
128_item_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part)
129{
130 struct Source *source = data;
131
132 if (!strcmp(part, "name"))
133 {
134 return strdup(source->name);
135 }
136
137 return NULL;
138}
139
140static void
141_item_del(void *data, Evas_Object *obj EINA_UNUSED)
142{
143 struct Source *source = data;
144
145 eina_stringshare_del(source->name);
146 free(source);
147}
148
149static void
150_volume_changed_cb(void *data, Evas_Object *o,
151 void *event_info EINA_UNUSED)
152{
153 struct Source *source = data;
154 double val = elm_slider_value_get(o);
155 pa_volume_t v = INT_TO_PA_VOLUME(val);
156
157 pa_cvolume_set(&source->volume, source->volume.channels, v);
158
159 epulse_source_volume_set(source->index, source->volume);
160}
161
162static void
163_mute_changed_cb(void *data, Evas_Object *o EINA_UNUSED,
164 void *event_info EINA_UNUSED)
165{
166 struct Source *source = data;
167
168 if (!epulse_source_mute_set(source->index, source->mute))
169 {
170 ERR("Could not mute the source: %d", source->index);
171 source->mute = !source->mute;
172 return;
173 }
174}
175
176static Evas_Object *
177_item_content_get(void *data, Evas_Object *obj, const char *part)
178{
179 Evas_Object *item = NULL;
180 struct Source *source = data;
181
182 if (!strcmp(part, "slider"))
183 {
184 pa_volume_t vol = pa_cvolume_avg(&source->volume);
185 item = elm_slider_add(obj);
186 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
187
188 elm_slider_unit_format_set(item, "%1.0f");
189 elm_slider_indicator_format_set(item, "%1.0f");
190 elm_slider_span_size_set(item, 120);
191 elm_slider_min_max_set(item, 0.0, 100.0);
192 elm_slider_value_set(item, PA_VOLUME_TO_INT(vol));
193 evas_object_smart_callback_add(item, "delay,changed",
194 _volume_changed_cb, source);
195 }
196 else if (!strcmp(part, "mute"))
197 {
198 item = elm_check_add(obj);
199 EINA_SAFETY_ON_NULL_RETURN_VAL(item, NULL);
200
201 elm_object_style_set(item, "toggle");
202 elm_object_part_text_set(item, "off", "Mute");
203 elm_object_part_text_set(item, "on", "Unmute");
204
205 elm_check_state_set(item, source->mute);
206 elm_check_state_pointer_set(item, &source->mute);
207 evas_object_smart_callback_add(item, "changed", _mute_changed_cb,
208 source);
209 }
210
211 return item;
212}
213
214Evas_Object *
215sources_view_add(Evas_Object *parent)
216{
217 Evas_Object *layout;
218 struct Sources_View *sv;
219
220 sv = calloc(1, sizeof(struct Sources_View));
221 EINA_SAFETY_ON_NULL_RETURN_VAL(sv, NULL);
222
223 layout = epulse_layout_add(parent, "sources", "default");
224 EINA_SAFETY_ON_NULL_GOTO(layout, err);
225
226 evas_object_event_callback_add(layout, EVAS_CALLBACK_DEL, _del_cb, sv);
227
228 sv->genlist = elm_genlist_add(layout);
229 EINA_SAFETY_ON_NULL_GOTO(sv->genlist, err_genlist);
230
231 sv->source_added = ecore_event_handler_add(SOURCE_ADDED, _source_add_cb, sv);
232 sv->source_added = ecore_event_handler_add(SOURCE_CHANGED,
233 _source_changed_cb, sv);
234 sv->source_removed = ecore_event_handler_add(SOURCE_REMOVED,
235 _source_removed_cb, sv);
236
237 sv->itc = elm_genlist_item_class_new();
238 EINA_SAFETY_ON_NULL_GOTO(sv->itc, err_genlist);
239 sv->itc->item_style = "sources";
240 sv->itc->func.text_get = _item_text_get;
241 sv->itc->func.content_get = _item_content_get;
242 sv->itc->func.del = _item_del;
243
244 evas_object_data_set(layout, SOURCES_KEY, sv);
245 elm_layout_content_set(layout, "list", sv->genlist);
246
247 evas_object_size_hint_weight_set(sv->genlist, EVAS_HINT_EXPAND,
248 EVAS_HINT_EXPAND);
249 evas_object_size_hint_align_set(sv->genlist, EVAS_HINT_FILL,
250 EVAS_HINT_FILL);
251
252 return layout;
253
254 err_genlist:
255 ecore_event_handler_del(sv->source_added);
256 ecore_event_handler_del(sv->source_changed);
257 ecore_event_handler_del(sv->source_removed);
258 free(layout);
259 err:
260 free(sv);
261
262 return NULL;
263}
diff --git a/src/bin/sources_view.h b/src/bin/sources_view.h
new file mode 100644
index 0000000..62697fe
--- /dev/null
+++ b/src/bin/sources_view.h
@@ -0,0 +1,13 @@
1#ifndef _SOURCES_VIEW_H_
2#define _SOURCES_VIEW_H_
3
4#include "common.h"
5
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
9
10Evas_Object *sources_view_add(Evas_Object *parent);
11
12
13#endif /* _SOURCES_VIEW_H_ */
diff --git a/src/lib/common.c b/src/lib/common.c
new file mode 100644
index 0000000..8b7f805
--- /dev/null
+++ b/src/lib/common.c
@@ -0,0 +1,64 @@
1#include "common.h"
2
3int _log_domain = -1;
4
5Eina_Bool
6epulse_common_init(const char *domain)
7{
8 EINA_SAFETY_ON_NULL_RETURN_VAL(domain, EINA_FALSE);
9 if (!eina_init())
10 {
11 fprintf(stderr, "Could not init eina\n");
12 return EINA_FALSE;
13 }
14
15 _log_domain = eina_log_domain_register(domain, NULL);
16 if (_log_domain < 0)
17 {
18 EINA_LOG_CRIT("Could not create log domain '%s'", domain);
19 goto err_log;
20 }
21
22 if (!ecore_init())
23 {
24 CRIT("Could not init ecore");
25 goto err_ecore;
26 }
27
28 return EINA_TRUE;
29
30 err_ecore:
31 eina_log_domain_unregister(_log_domain);
32 _log_domain = -1;
33 err_log:
34 eina_shutdown();
35 return EINA_FALSE;
36}
37
38void
39epulse_common_shutdown(void)
40{
41 eina_shutdown();
42
43 eina_log_domain_unregister(_log_domain);
44 _log_domain = -1;
45
46 ecore_shutdown();
47}
48
49Evas_Object *epulse_layout_add(Evas_Object *parent, const char *group,
50 const char *style)
51{
52 EINA_SAFETY_ON_NULL_RETURN_VAL(group, NULL);
53 EINA_SAFETY_ON_NULL_RETURN_VAL(style, NULL);
54
55 Evas_Object *layout = elm_layout_add(parent);
56 if (!elm_layout_theme_set(layout, "layout", group, style))
57 {
58 CRIT("No theme for 'elm/layout/%s/%s' at %s", group, style, EPULSE_THEME);
59 evas_object_del(layout);
60 return NULL;
61 }
62
63 return layout;
64}
diff --git a/src/lib/common.h b/src/lib/common.h
new file mode 100644
index 0000000..66b3925
--- /dev/null
+++ b/src/lib/common.h
@@ -0,0 +1,31 @@
1#ifndef __COMMON_H__
2#define __COMMON_H__
3
4#ifndef _GNU_SOURCE
5#define _GNU_SOURCE
6#endif
7
8#include <Ecore.h>
9#include <Evas.h>
10#include <Elementary.h>
11
12#include <stdlib.h>
13#include <stdio.h>
14#include <string.h>
15
16#define EPULSE_THEME PACKAGE_DATA_DIR"/data/themes/default.edj"
17
18EAPI extern int _log_domain;
19
20#define CRIT(...) EINA_LOG_DOM_CRIT(_log_domain, __VA_ARGS__)
21#define ERR(...) EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__)
22#define WRN(...) EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__)
23#define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__)
24#define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__)
25
26EAPI Eina_Bool epulse_common_init(const char *domain);
27EAPI void epulse_common_shutdown(void);
28EAPI Evas_Object *epulse_layout_add(Evas_Object *parent, const char *group,
29 const char *style);
30
31#endif /* __COMMON_H__ */
diff --git a/src/lib/epulse.c b/src/lib/epulse.c
new file mode 100644
index 0000000..f0e3af9
--- /dev/null
+++ b/src/lib/epulse.c
@@ -0,0 +1,781 @@
1#include "epulse.h"
2
3typedef struct _Epulse_Context Epulse_Context;
4struct _Epulse_Context {
5 pa_mainloop_api api;
6 pa_context *context;
7 pa_context_state_t state;
8 void *data;
9};
10
11static unsigned int _init_count = 0;
12static Epulse_Context *ctx = NULL;
13extern pa_mainloop_api functable;
14
15int SINK_ADDED = 0;
16int SINK_CHANGED = 0;
17int SINK_DEFAULT = 0;
18int SINK_REMOVED = 0;
19int SINK_INPUT_ADDED = 0;
20int SINK_INPUT_CHANGED = 0;
21int SINK_INPUT_REMOVED = 0;
22int SOURCE_ADDED = 0;
23int SOURCE_CHANGED = 0;
24int SOURCE_REMOVED = 0;
25int SOURCE_INPUT_ADDED = 0;
26int SOURCE_INPUT_REMOVED = 0;
27
28static void
29_event_free_cb(void *user_data EINA_UNUSED, void *func_data)
30{
31 Epulse_Event *ev = func_data;
32
33 if (ev->name)
34 free(ev->name);
35
36 free(ev);
37}
38
39static void
40_event_sink_input_free_cb(void *user_data EINA_UNUSED, void *func_data)
41{
42 Epulse_Event_Sink_Input *ev = func_data;
43
44 if (ev->base.name)
45 free(ev->base.name);
46
47 if (ev->icon)
48 free(ev->icon);
49
50 free(ev);
51}
52
53static void
54_event_sink_free_cb(void *user_data EINA_UNUSED, void *func_data)
55{
56 Epulse_Event_Sink *ev = func_data;
57 Port *port;
58
59 if (ev->base.name)
60 free(ev->base.name);
61
62 EINA_LIST_FREE(ev->ports, port)
63 {
64 free(port->name);
65 free(port);
66 }
67
68 free(ev);
69}
70
71static void
72_sink_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
73 void *userdata EINA_UNUSED)
74{
75 Epulse_Event_Sink *ev;
76 Port *port;
77 uint32_t i;
78
79 if (eol < 0)
80 {
81 if (pa_context_errno(c) == PA_ERR_NOENTITY)
82 return;
83
84 ERR("Sink callback failure");
85 return;
86 }
87
88 if (eol > 0)
89 return;
90
91 DBG("sink index: %d\nsink name: %s", info->index,
92 info->name);
93
94 ev = calloc(1, sizeof(Epulse_Event_Sink));
95 ev->base.index = info->index;
96 ev->base.name = strdup(info->description);
97 ev->base.volume = info->volume;
98 ev->base.mute = !!info->mute;
99
100 for (i = 0; i < info->n_ports; i++)
101 {
102 port = calloc(1, sizeof(Port));
103 EINA_SAFETY_ON_NULL_GOTO(port, error);
104
105 port->available = !!info->ports[i]->available;
106 port->priority = info->ports[i]->priority;
107 port->name = strdup(info->ports[i]->description ?:
108 info->ports[i]->name);
109 ev->ports = eina_list_append(ev->ports, port);
110 if (info->ports[i]->name == info->active_port->name)
111 port->active = EINA_TRUE;
112 }
113
114 ecore_event_add(SINK_ADDED, ev, _event_sink_free_cb, NULL);
115 return;
116
117 error:
118 _event_sink_free_cb(NULL, ev);
119}
120
121static void
122_sink_changed_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
123 void *userdata EINA_UNUSED)
124{
125 Epulse_Event_Sink *ev;
126 Port *port;
127 uint32_t i;
128
129 if (eol < 0)
130 {
131 if (pa_context_errno(c) == PA_ERR_NOENTITY)
132 return;
133
134 ERR("Sink callback failure");
135 return;
136 }
137
138 if (eol > 0)
139 return;
140
141 DBG("sink index: %d\nsink name: %s", info->index,
142 info->name);
143
144 ev = calloc(1, sizeof(Epulse_Event_Sink));
145 ev->base.index = info->index;
146 ev->base.name = strdup(info->name);
147 ev->base.volume = info->volume;
148 ev->base.mute = !!info->mute;
149
150 for (i = 0; i < info->n_ports; i++)
151 {
152 port = calloc(1, sizeof(Port));
153 EINA_SAFETY_ON_NULL_GOTO(port, error);
154
155 port->priority = info->ports[i]->priority;
156 port->available = !!info->ports[i]->available;
157 port->name = strdup(info->ports[i]->description ?:
158 info->ports[i]->name);
159 ev->ports = eina_list_append(ev->ports, port);
160 if (info->ports[i]->name == info->active_port->name)
161 port->active = EINA_TRUE;
162 }
163
164 ecore_event_add(SINK_CHANGED, ev, _event_sink_free_cb, NULL);
165 return;
166
167 error:
168 _event_sink_free_cb(NULL, ev);
169}
170
171static void
172_sink_remove_cb(int index, void *data EINA_UNUSED)
173{
174 Epulse_Event_Sink *ev;
175 DBG("Removing sink: %d", index);
176
177 ev = calloc(1, sizeof(Epulse_Event_Sink));
178 ev->base.index = index;
179
180 ecore_event_add(SINK_REMOVED, ev, _event_sink_free_cb, NULL);
181}
182
183static const char *
184_icon_from_properties(pa_proplist *l)
185{
186 const char *t;
187
188 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
189 return t;
190
191 if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
192 return t;
193
194 if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
195 return t;
196
197 if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE)))
198 {
199
200 if (strcmp(t, "video") == 0 ||
201 strcmp(t, "phone") == 0)
202 return t;
203
204 if (strcmp(t, "music") == 0)
205 return "audio";
206
207 if (strcmp(t, "game") == 0)
208 return "applications-games";
209
210 if (strcmp(t, "event") == 0)
211 return "dialog-information";
212 }
213
214 return "audio-card";
215}
216
217static void
218_sink_input_cb(pa_context *c EINA_UNUSED, const pa_sink_input_info *info,
219 int eol, void *userdata EINA_UNUSED)
220{
221 Epulse_Event_Sink_Input *ev;
222
223 if (eol < 0)
224 {
225 if (pa_context_errno(c) == PA_ERR_NOENTITY)
226 return;
227
228 ERR("Sink input callback failure");
229 return;
230 }
231
232 if (eol > 0)
233 return;
234
235 DBG("sink input index: %d\nsink input name: %s", info->index,
236 info->name);
237
238 ev = calloc(1, sizeof(Epulse_Event_Sink_Input));
239 ev->base.index = info->index;
240 ev->base.name = strdup(info->name);
241 ev->base.volume = info->volume;
242 ev->base.mute = !!info->mute;
243 ev->sink = info->sink;
244 ev->icon = strdup(_icon_from_properties(info->proplist));
245
246 ecore_event_add(SINK_INPUT_ADDED, ev, _event_sink_input_free_cb, NULL);
247}
248
249static void
250_sink_input_changed_cb(pa_context *c EINA_UNUSED,
251 const pa_sink_input_info *info, int eol,
252 void *userdata EINA_UNUSED)
253{
254 Epulse_Event_Sink_Input *ev;
255
256 if (eol < 0)
257 {
258 if (pa_context_errno(c) == PA_ERR_NOENTITY)
259 return;
260
261 ERR("Sink input changed callback failure");
262 return;
263 }
264
265 if (eol > 0)
266 return;
267
268 DBG("sink input changed index: %d\n", info->index);
269
270 ev = calloc(1, sizeof(Epulse_Event_Sink_Input));
271 ev->base.index = info->index;
272 ev->base.volume = info->volume;
273 ev->base.mute = !!info->mute;
274
275 ecore_event_add(SINK_INPUT_CHANGED, ev, _event_sink_input_free_cb,
276 NULL);
277}
278
279static void
280_sink_input_remove_cb(int index, void *data EINA_UNUSED)
281{
282 Epulse_Event_Sink_Input *ev;
283
284 DBG("Removing sink input: %d", index);
285
286 ev = calloc(1, sizeof(Epulse_Event_Sink_Input));
287 ev->base.index = index;
288
289 ecore_event_add(SINK_INPUT_REMOVED, ev, _event_sink_input_free_cb, NULL);
290}
291
292static void
293_source_cb(pa_context *c EINA_UNUSED, const pa_source_info *info,
294 int eol, void *userdata EINA_UNUSED)
295{
296 Epulse_Event *ev;
297
298 if (eol < 0)
299 {
300 if (pa_context_errno(c) == PA_ERR_NOENTITY)
301 return;
302
303 ERR("Source callback failure");
304 return;
305 }
306
307 if (eol > 0)
308 return;
309
310 ev = calloc(1, sizeof(Epulse_Event));
311 ev->index = info->index;
312 ev->name = strdup(info->name);
313 ev->volume = info->volume;
314 ev->mute = !!info->mute;
315
316 ecore_event_add(SOURCE_ADDED, ev, _event_free_cb, NULL);
317}
318
319static void
320_source_changed_cb(pa_context *c EINA_UNUSED,
321 const pa_source_info *info, int eol,
322 void *userdata EINA_UNUSED)
323{
324 Epulse_Event *ev;
325
326 if (eol < 0)
327 {
328 if (pa_context_errno(c) == PA_ERR_NOENTITY)
329 return;
330
331 ERR("Source changed callback failure");
332 return;
333 }
334
335 if (eol > 0)
336 return;
337
338 DBG("source changed index: %d\n", info->index);
339
340 ev = calloc(1, sizeof(Epulse_Event));
341 ev->index = info->index;
342 ev->volume = info->volume;
343 ev->mute = !!info->mute;
344
345 ecore_event_add(SOURCE_CHANGED, ev, _event_free_cb,
346 NULL);
347}
348
349static void
350_source_remove_cb(int index, void *data EINA_UNUSED)
351{
352 Epulse_Event *ev;
353
354 DBG("Removing source: %d", index);
355
356 ev = calloc(1, sizeof(Epulse_Event));
357 ev->index = index;
358
359 ecore_event_add(SOURCE_REMOVED, ev, _event_free_cb, NULL);
360}
361
362static void
363_sink_default_cb(pa_context *c EINA_UNUSED, const pa_sink_info *info, int eol,
364 void *userdata EINA_UNUSED)
365{
366 Epulse_Event_Sink *ev;
367
368 if (eol < 0)
369 {
370 if (pa_context_errno(c) == PA_ERR_NOENTITY)
371 return;
372
373 ERR("Sink callback failure");
374 return;
375 }
376
377 if (eol > 0)
378 return;
379
380 DBG("sink index: %d\nsink name: %s", info->index,
381 info->name);
382
383 ev = calloc(1, sizeof(Epulse_Event_Sink));
384 ev->base.index = info->index;
385 ev->base.name = strdup(info->description);
386 ev->base.volume = info->volume;
387 ev->base.mute = !!info->mute;
388
389 ecore_event_add(SINK_DEFAULT, ev, _event_sink_free_cb, NULL);
390}
391
392static void
393_server_info_cb(pa_context *c, const pa_server_info *info,
394 void *userdata)
395{
396 pa_operation *o;
397
398 if (!(o = pa_context_get_sink_info_by_name(c, info->default_sink_name,
399 _sink_default_cb, userdata)))
400 {
401 ERR("pa_context_get_sink_info_by_name() failed");
402 return;
403 }
404 pa_operation_unref(o);
405}
406
407static void
408_subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
409 uint32_t index, void *data)
410{
411 pa_operation *o;
412
413 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
414 case PA_SUBSCRIPTION_EVENT_SINK:
415 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
416 PA_SUBSCRIPTION_EVENT_REMOVE)
417 _sink_remove_cb(index, data);
418 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
419 PA_SUBSCRIPTION_EVENT_NEW)
420 {
421 if (!(o = pa_context_get_sink_info_by_index(c, index,
422 _sink_cb, data)))
423 {
424 ERR("pa_context_get_sink_info_by_index() failed");
425 return;
426 }
427 pa_operation_unref(o);
428 }
429 else
430 {
431 if (!(o = pa_context_get_sink_info_by_index(c, index,
432 _sink_changed_cb,
433 data)))
434 {
435 ERR("pa_context_get_sink_info_by_index() failed");
436 return;
437 }
438 pa_operation_unref(o);
439 }
440 break;
441
442 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
443 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
444 PA_SUBSCRIPTION_EVENT_REMOVE)
445 _sink_input_remove_cb(index, data);
446 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
447 PA_SUBSCRIPTION_EVENT_NEW)
448 {
449 if (!(o = pa_context_get_sink_input_info(c, index,
450 _sink_input_cb, data)))
451 {
452 ERR("pa_context_get_sink_input_info() failed");
453 return;
454 }
455 pa_operation_unref(o);
456 }
457 else
458 {
459 if (!(o = pa_context_get_sink_input_info(c, index,
460 _sink_input_changed_cb,
461 data)))
462 {
463 ERR("pa_context_get_sink_input_info() failed");
464 return;
465 }
466 pa_operation_unref(o);
467 }
468 break;
469
470 case PA_SUBSCRIPTION_EVENT_SOURCE:
471 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
472 PA_SUBSCRIPTION_EVENT_REMOVE)
473 _source_remove_cb(index, data);
474 else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
475 PA_SUBSCRIPTION_EVENT_NEW)
476 {
477 if (!(o = pa_context_get_source_info_by_index(c, index,
478 _source_cb, data)))
479 {
480 ERR("pa_context_get_source_info() failed");
481 return;
482 }
483 pa_operation_unref(o);
484 }
485 else
486 {
487 if (!(o = pa_context_get_source_info_by_index(c, index,
488 _source_changed_cb,
489 data)))
490 {
491 ERR("pa_context_get_source_info() failed");
492 return;
493 }
494 pa_operation_unref(o);
495 }
496 break;
497
498 default:
499 WRN("Event not handled");
500 break;
501 }
502}
503
504static void
505_epulse_pa_state_cb(pa_context *context, void *data EINA_UNUSED)
506{
507 pa_operation *o;
508
509 switch (pa_context_get_state(context))
510 {
511 case PA_CONTEXT_UNCONNECTED:
512 case PA_CONTEXT_CONNECTING:
513 case PA_CONTEXT_AUTHORIZING:
514 case PA_CONTEXT_SETTING_NAME:
515 break;
516
517 case PA_CONTEXT_READY:
518 {
519 pa_context_set_subscribe_callback(context, _subscribe_cb, ctx);
520 if (!(o = pa_context_subscribe(context, (pa_subscription_mask_t)
521 (PA_SUBSCRIPTION_MASK_SINK|
522 PA_SUBSCRIPTION_MASK_SOURCE|
523 PA_SUBSCRIPTION_MASK_SINK_INPUT|
524 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
525 PA_SUBSCRIPTION_MASK_CLIENT|
526 PA_SUBSCRIPTION_MASK_SERVER|
527 PA_SUBSCRIPTION_MASK_CARD),
528 NULL, NULL)))
529 {
530 ERR("pa_context_subscribe() failed");
531 return;
532 }
533 pa_operation_unref(o);
534
535 if (!(o = pa_context_get_sink_info_list(context, _sink_cb, ctx)))
536 {
537 ERR("pa_context_get_sink_info_list() failed");
538 return;
539 }
540 pa_operation_unref(o);
541
542 if (!(o = pa_context_get_sink_input_info_list(context,
543 _sink_input_cb,
544 ctx)))
545 {
546 ERR("pa_context_get_sink_input_info_list() failed");
547 return;
548 }
549 pa_operation_unref(o);
550
551 if (!(o = pa_context_get_source_info_list(context, _source_cb,
552 ctx)))
553 {
554 ERR("pa_context_get_source_info_list() failed");
555 return;
556 }
557 pa_operation_unref(o);
558
559 if (!(o = pa_context_get_server_info(context, _server_info_cb,
560 ctx)))
561 {
562 ERR("pa_context_get_server_info() failed");
563 return;
564 }
565 pa_operation_unref(o);
566 break;
567 }
568
569 case PA_CONTEXT_FAILED:
570 pa_context_unref(context);
571 context = NULL;
572 return;
573
574 case PA_CONTEXT_TERMINATED:
575 default:
576 exit(0);
577 return;
578 }
579}
580
581int
582epulse_init(void)
583{
584 pa_proplist *proplist;
585
586 if (_init_count > 0)
587 goto end;
588
589 ctx = calloc(1, sizeof(Epulse_Context));
590 if (!ctx)
591 {
592 ERR("Could not create Epulse Context");
593 return 0;
594 }
595
596 SINK_ADDED = ecore_event_type_new();
597 SINK_CHANGED = ecore_event_type_new();
598 SINK_DEFAULT = ecore_event_type_new();
599 SINK_REMOVED = ecore_event_type_new();
600 SINK_INPUT_ADDED = ecore_event_type_new();
601 SINK_INPUT_CHANGED = ecore_event_type_new();
602 SINK_INPUT_REMOVED = ecore_event_type_new();
603 SOURCE_ADDED = ecore_event_type_new();
604 SOURCE_CHANGED = ecore_event_type_new();
605 SOURCE_REMOVED = ecore_event_type_new();
606 SOURCE_INPUT_ADDED = ecore_event_type_new();
607 SOURCE_INPUT_REMOVED = ecore_event_type_new();
608
609 ctx->api = functable;
610 ctx->api.userdata = ctx;
611
612 proplist = pa_proplist_new();
613 pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "Efl Volume Control");
614 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID,
615 "org.enlightenment.volumecontrol");
616 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
617 ctx->context = pa_context_new_with_proplist(&(ctx->api), NULL, proplist);
618 if (!ctx->context)
619 {
620 WRN("Could not connect to pulse");
621 goto err;
622 }
623
624 pa_context_set_state_callback(ctx->context, _epulse_pa_state_cb, ctx);
625 pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL);
626 pa_proplist_free(proplist);
627
628 end:
629 _init_count++;
630 return _init_count;
631
632 err:
633 pa_proplist_free(proplist);
634 free(ctx);
635 return 0;
636}
637
638void
639epulse_shutdown(void)
640{
641 if (_init_count == 0)
642 return;
643
644 _init_count--;
645 if (_init_count > 0)
646 return;
647
648 pa_context_unref(ctx->context);
649 free(ctx);
650 ctx = NULL;
651}
652
653Eina_Bool
654epulse_source_volume_set(int index, pa_cvolume volume)
655{
656 pa_operation* o;
657 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
658
659 if (!(o = pa_context_set_source_volume_by_index(ctx->context,
660 index, &volume, NULL, NULL)))
661 {
662 ERR("pa_context_set_source_volume_by_index() failed");
663 return EINA_FALSE;
664 }
665
666 return EINA_TRUE;
667}
668
669Eina_Bool
670epulse_source_mute_set(int index, Eina_Bool mute)
671{
672 pa_operation* o;
673 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
674
675 if (!(o = pa_context_set_source_mute_by_index(ctx->context,
676 index, mute, NULL, NULL)))
677 {
678 ERR("pa_context_set_source_mute() failed");
679 return EINA_FALSE;
680 }
681
682 return EINA_TRUE;
683}
684
685Eina_Bool
686epulse_sink_volume_set(int index, pa_cvolume volume)
687{
688 pa_operation* o;
689 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
690
691 if (!(o = pa_context_set_sink_volume_by_index(ctx->context,
692 index, &volume, NULL, NULL)))
693 {
694 ERR("pa_context_set_sink_volume_by_index() failed");
695 return EINA_FALSE;
696 }
697
698 return EINA_TRUE;
699}
700
701Eina_Bool
702epulse_sink_mute_set(int index, Eina_Bool mute)
703{
704 pa_operation* o;
705 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
706
707 if (!(o = pa_context_set_sink_mute_by_index(ctx->context,
708 index, mute, NULL, NULL)))
709 {
710 ERR("pa_context_set_sink_mute() failed");
711 return EINA_FALSE;
712 }
713
714 return EINA_TRUE;
715}
716
717Eina_Bool
718epulse_sink_input_volume_set(int index, pa_cvolume volume)
719{
720 pa_operation* o;
721 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
722
723 if (!(o = pa_context_set_sink_input_volume(ctx->context,
724 index, &volume, NULL, NULL)))
725 {
726 ERR("pa_context_set_sink_input_volume_by_index() failed");
727 return EINA_FALSE;
728 }
729
730 return EINA_TRUE;
731}
732
733Eina_Bool
734epulse_sink_input_mute_set(int index, Eina_Bool mute)
735{
736 pa_operation* o;
737 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
738
739 if (!(o = pa_context_set_sink_input_mute(ctx->context,
740 index, mute, NULL, NULL)))
741 {
742 ERR("pa_context_set_sink_input_mute() failed");
743 return EINA_FALSE;
744 }
745
746 return EINA_TRUE;
747}
748
749Eina_Bool
750epulse_sink_input_move(int index, int sink_index)
751{
752 pa_operation* o;
753 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
754
755 if (!(o = pa_context_move_sink_input_by_index(ctx->context,
756 index, sink_index, NULL,
757 NULL)))
758 {
759 ERR("pa_context_move_sink_input_by_index() failed");
760 return EINA_FALSE;
761 }
762
763 return EINA_TRUE;
764}
765
766Eina_Bool
767epulse_sink_port_set(int index, const char *port)
768{
769 pa_operation* o;
770 EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, EINA_FALSE);
771
772 if (!(o = pa_context_set_source_port_by_index(ctx->context,
773 index, port, NULL,
774 NULL)))
775 {
776 ERR("pa_context_set_source_port_by_index() failed");
777 return EINA_FALSE;
778 }
779
780 return EINA_TRUE;
781}
diff --git a/src/lib/epulse.h b/src/lib/epulse.h
new file mode 100644
index 0000000..aaace56
--- /dev/null
+++ b/src/lib/epulse.h
@@ -0,0 +1,63 @@
1#include "common.h"
2
3#include <pulse/pulseaudio.h>
4
5#define PA_VOLUME_TO_INT(_vol) \
6 ((_vol*100+PA_VOLUME_NORM/2)/PA_VOLUME_NORM)
7#define INT_TO_PA_VOLUME(_vol) \
8 ((PA_VOLUME_NORM*_vol-PA_VOLUME_NORM/2)/100)
9
10typedef struct _Port Port;
11struct _Port {
12 Eina_Bool active;
13 Eina_Bool available;
14 int priority;
15 char *name;
16 char *description;
17};
18
19typedef struct _Epulse_Event Epulse_Event;
20struct _Epulse_Event
21{
22 int index;
23 char *name;
24 pa_cvolume volume;
25 Eina_Bool mute;
26};
27
28typedef struct _Epulse_Event_Sink Epulse_Event_Sink;
29struct _Epulse_Event_Sink {
30 Epulse_Event base;
31 Eina_List *ports;
32};
33
34typedef struct _Epulse_Event_Sink_Input Epulse_Event_Sink_Input;
35struct _Epulse_Event_Sink_Input {
36 Epulse_Event base;
37 int sink;
38 char *icon;
39};
40
41EAPI extern int SINK_ADDED;
42EAPI extern int SINK_CHANGED;
43EAPI extern int SINK_DEFAULT;
44EAPI extern int SINK_REMOVED;
45EAPI extern int SINK_INPUT_ADDED;
46EAPI extern int SINK_INPUT_CHANGED;
47EAPI extern int SINK_INPUT_REMOVED;
48EAPI extern int SOURCE_ADDED;
49EAPI extern int SOURCE_CHANGED;
50EAPI extern int SOURCE_REMOVED;
51EAPI extern int SOURCE_INPUT_ADDED;
52EAPI extern int SOURCE_INPUT_REMOVED;
53
54EAPI int epulse_init(void);
55EAPI Eina_Bool epulse_source_volume_set(int index, pa_cvolume volume);
56EAPI Eina_Bool epulse_source_mute_set(int index, Eina_Bool mute);
57EAPI Eina_Bool epulse_sink_volume_set(int index, pa_cvolume volume);
58EAPI Eina_Bool epulse_sink_mute_set(int index, Eina_Bool mute);
59EAPI Eina_Bool epulse_sink_port_set(int index, const char *port);
60EAPI Eina_Bool epulse_sink_input_volume_set(int index, pa_cvolume volume);
61EAPI Eina_Bool epulse_sink_input_mute_set(int index, Eina_Bool mute);
62EAPI Eina_Bool epulse_sink_input_move(int index, int sink_index);
63EAPI void epulse_shutdown(void);
diff --git a/src/epulse_ml.c b/src/lib/epulse_ml.c
index 6686d3c..9c9a2b9 100644
--- a/src/epulse_ml.c
+++ b/src/lib/epulse_ml.c
@@ -30,9 +30,9 @@ static Ecore_Fd_Handler_Flags
30map_flags_to_ecore(pa_io_event_flags_t flags) 30map_flags_to_ecore(pa_io_event_flags_t flags)
31{ 31{
32 return (Ecore_Fd_Handler_Flags)((flags & PA_IO_EVENT_INPUT ? ECORE_FD_READ : 0) | 32 return (Ecore_Fd_Handler_Flags)((flags & PA_IO_EVENT_INPUT ? ECORE_FD_READ : 0) |
33 (flags & PA_IO_EVENT_OUTPUT ? ECORE_FD_WRITE : 0) | 33 (flags & PA_IO_EVENT_OUTPUT ? ECORE_FD_WRITE : 0) |
34 (flags & PA_IO_EVENT_ERROR ? ECORE_FD_ERROR : 0) | 34 (flags & PA_IO_EVENT_ERROR ? ECORE_FD_ERROR : 0) |
35 (flags & PA_IO_EVENT_HANGUP ? ECORE_FD_READ : 0)); 35 (flags & PA_IO_EVENT_HANGUP ? ECORE_FD_READ : 0));
36} 36}
37 37
38static Eina_Bool 38static Eina_Bool
@@ -62,9 +62,9 @@ _ecore_io_wrapper(void *data, Ecore_Fd_Handler *handler)
62 } 62 }
63 63
64 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_WRITE)) 64 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_WRITE))
65 flags |= PA_IO_EVENT_OUTPUT; 65 flags |= PA_IO_EVENT_OUTPUT;
66 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR)) 66 if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR))
67 flags |= PA_IO_EVENT_ERROR; 67 flags |= PA_IO_EVENT_ERROR;
68 68
69 event->callback(event->mainloop, event, fd, flags, event->userdata); 69 event->callback(event->mainloop, event, fd, flags, event->userdata);
70 70
@@ -193,7 +193,7 @@ void
193_ecore_pa_time_free(pa_time_event *event) 193_ecore_pa_time_free(pa_time_event *event)
194{ 194{
195 if (event->timer) 195 if (event->timer)
196 ecore_timer_del(event->timer); 196 ecore_timer_del(event->timer);
197 197
198 event->timer = NULL; 198 event->timer = NULL;
199 199
@@ -262,7 +262,7 @@ void
262_ecore_pa_defer_free(pa_defer_event *event) 262_ecore_pa_defer_free(pa_defer_event *event)
263{ 263{
264 if (event->idler) 264 if (event->idler)
265 ecore_idler_del(event->idler); 265 ecore_idler_del(event->idler);
266 266
267 event->idler = NULL; 267 event->idler = NULL;
268 268
diff --git a/src/main.c b/src/main.c
deleted file mode 100644
index df0808b..0000000
--- a/src/main.c
+++ /dev/null
@@ -1,18 +0,0 @@
1#include <Elementary.h>
2
3EAPI int elm_main(int argc, char **argv)
4{
5 int args;
6
7 elm_run();
8
9
10 return 0;
11
12}
13
14/*
15 * Create the default main() that will work with both quicklaunch or
16 * regular applications.
17 */
18ELM_MAIN()
diff --git a/src/module/e_mod_main.c b/src/module/e_mod_main.c
new file mode 100644
index 0000000..232b820
--- /dev/null
+++ b/src/module/e_mod_main.c
@@ -0,0 +1,586 @@
1#include <common.h>
2
3#include <e.h>
4#include <Eina.h>
5#include <epulse.h>
6#include "e_mod_main.h"
7
8/* module requirements */
9EAPI E_Module_Api e_modapi =
10 {
11 E_MODULE_API_VERSION,
12 "Pulse Mixer"
13 };
14
15/* necessary forward delcaration */
16static E_Gadcon_Client *_gc_init(E_Gadcon *gc, const char *name,
17 const char *id, const char *style);
18static void _gc_shutdown(E_Gadcon_Client *gcc);
19static void _gc_orient(E_Gadcon_Client *gcc,
20 E_Gadcon_Orient orient);
21static const char *_gc_label(const E_Gadcon_Client_Class *client_class);
22static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class,
23 Evas *evas);
24static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class);
25
26static const E_Gadcon_Client_Class _gadcon_class =
27 {
28 GADCON_CLIENT_CLASS_VERSION,
29 "pulse_mixer",
30 {
31 _gc_init, _gc_shutdown,
32 _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL,
33 e_gadcon_site_is_not_toolbar
34 },
35 E_GADCON_CLIENT_STYLE_PLAIN
36 };
37
38typedef struct _Sink Sink;
39struct _Sink {
40 int index;
41 pa_cvolume volume;
42 int mute;
43 char *name;
44};
45
46typedef struct _Context Context;
47struct _Context
48{
49 char *theme;
50 Ecore_Exe *epulse;
51 Ecore_Event_Handler *epulse_event_handler;
52 Ecore_Event_Handler *sink_default_handler;
53 Ecore_Event_Handler *sink_changed_handler;
54 Ecore_Event_Handler *sink_added_handler;
55 Ecore_Event_Handler *sink_removed_handler;
56 Sink *sink_default;
57 E_Module *module;
58 Eina_List *instances;
59 Eina_List *sinks;
60 E_Menu *menu;
61};
62
63typedef struct _Instance Instance;
64struct _Instance
65{
66 E_Gadcon_Client *gcc;
67 E_Gadcon_Orient orient;
68
69 E_Gadcon_Popup *popup;
70 Evas_Object *gadget;
71 Evas_Object *list;
72 Evas_Object *slider;
73 Evas_Object *check;
74
75 int mute;
76};
77
78static Context *mixer_context = NULL;
79
80static void
81_mixer_popup_update(Instance *inst, int mute, int vol)
82{
83 e_widget_check_checked_set(inst->check, mute);
84 e_slider_value_set(inst->slider, vol);
85}
86
87static void
88_mixer_gadget_update(void)
89{
90 Edje_Message_Int_Set *msg;
91 Instance *inst;
92 Eina_List*l;
93
94 EINA_LIST_FOREACH(mixer_context->instances, l, inst)
95 {
96 pa_volume_t vol = pa_cvolume_avg(&mixer_context->sink_default->volume);
97 msg = alloca(sizeof(Edje_Message_Int_Set) + (2 * sizeof(int)));
98 msg->count = 3;
99 msg->val[0] = mixer_context->sink_default->mute;
100 msg->val[1] = PA_VOLUME_TO_INT(vol);
101 msg->val[2] = msg->val[1];
102 edje_object_message_send(inst->gadget, EDJE_MESSAGE_INT_SET, 0, msg);
103
104 edje_object_signal_emit(inst->gadget, "e,action,volume,change", "e");
105
106 if (inst->popup)
107 _mixer_popup_update(inst, mixer_context->sink_default->mute,
108 msg->val[1]);
109 }
110}
111
112static void
113_popup_del(Instance *inst)
114{
115 inst->slider = NULL;
116 inst->check = NULL;
117 E_FREE_FUNC(inst->popup, e_object_del);
118}
119
120static void
121_popup_del_cb(void *obj)
122{
123 _popup_del(e_object_data_get(obj));
124}
125
126static void
127_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED)
128{
129 Instance *inst = data;
130
131 E_FREE_FUNC(inst->popup, e_object_del);
132}
133
134static Eina_Bool
135_epulse_del_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
136 void *info EINA_UNUSED)
137{
138 mixer_context->epulse = NULL;
139 if (mixer_context->epulse_event_handler)
140 ecore_event_handler_del(mixer_context->epulse_event_handler);
141
142 return EINA_TRUE;
143}
144
145static void
146_epulse_exec_cb(void *data, void *data2 EINA_UNUSED)
147{
148 Instance *inst = data;
149
150 _popup_del(inst);
151 if (mixer_context->epulse)
152 return;
153
154 mixer_context->epulse = ecore_exe_run("epulse", NULL);
155 if (mixer_context->epulse_event_handler)
156 ecore_event_handler_del(mixer_context->epulse_event_handler);
157 mixer_context->epulse_event_handler =
158 ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _epulse_del_cb, NULL);
159}
160
161static void
162_check_changed_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
163 void *event EINA_UNUSED)
164{
165 Sink *s = mixer_context->sink_default;
166 s->mute = !s->mute;
167 if (!epulse_sink_mute_set(s->index, s->mute))
168 {
169 WRN("Could not mute the sink: %d", s->index);
170 s->mute = !s->mute;
171 return;
172 }
173
174 _mixer_gadget_update();
175}
176
177static void
178_slider_changed_cb(void *data EINA_UNUSED, Evas_Object *obj,
179 void *event EINA_UNUSED)
180{
181 int val;
182 pa_volume_t v;
183 Sink *s = mixer_context->sink_default;
184
185 val = (int)e_slider_value_get(obj);
186 v = INT_TO_PA_VOLUME(val);
187
188 pa_cvolume_set(&s->volume, s->volume.channels, v);
189 epulse_sink_volume_set(s->index, s->volume);
190}
191
192static Evas_Object *
193_popup_add_slider(Instance *inst)
194{
195 pa_volume_t vol = pa_cvolume_avg(&mixer_context->sink_default->volume);
196 int value = PA_VOLUME_TO_INT(vol);
197 Evas_Object *slider = e_slider_add(e_comp_get(inst->popup)->evas);
198 EINA_SAFETY_ON_NULL_RETURN_VAL(slider, NULL);
199
200 evas_object_show(slider);
201 e_slider_orientation_set(slider, 1);
202 e_slider_value_range_set(slider, 0.0, 100.0);
203 e_slider_value_format_display_set(slider, NULL);
204 evas_object_smart_callback_add(slider, "changed", _slider_changed_cb,
205 NULL);
206
207 e_slider_value_set(slider, value);
208 return slider;
209}
210
211static void
212_sink_selected_cb(void *data)
213{
214 Sink *s = data;
215
216 mixer_context->sink_default = s;
217 _mixer_gadget_update();
218}
219
220static void
221_popup_new(Instance *inst)
222{
223 Evas_Object *button, *list;
224 Evas *evas;
225 Evas_Coord mw, mh;
226 Sink *s;
227 Eina_List *l;
228 int pos = 0;
229
230 inst->popup = e_gadcon_popup_new(inst->gcc, 0);
231 evas = e_comp_get(inst->gcc)->evas;
232
233 list = e_widget_list_add(evas, 0, 0);
234
235 inst->list = e_widget_ilist_add(evas, 24, 24, NULL);
236 e_widget_size_min_set(inst->list, 120, 100);
237 e_widget_list_object_append(list, inst->list, 1, 1, 0.5);
238
239 EINA_LIST_FOREACH(mixer_context->sinks, l, s)
240 {
241 e_widget_ilist_append_full(inst->list, NULL, NULL, s->name,
242 _sink_selected_cb,
243 s, NULL);
244 if (mixer_context->sink_default == s)
245 e_widget_ilist_selected_set(inst->list, pos);
246
247 pos++;
248 }
249
250 inst->slider = _popup_add_slider(inst);
251 e_widget_list_object_append(list, inst->slider, 1, 1, 0.5);
252
253 inst->mute = mixer_context->sink_default->mute;
254 inst->check = e_widget_check_add(evas, "Mute",
255 &inst->mute);
256 e_widget_list_object_append(list, inst->check, 1, 9, 0.5);
257 evas_object_smart_callback_add(inst->check, "changed", _check_changed_cb,
258 NULL);
259
260 button = e_widget_button_add(evas, NULL, "preferences-system",
261 _epulse_exec_cb, inst, NULL);
262 e_widget_list_object_append(list, button, 1, 0, 0.5);
263
264 e_widget_size_min_get(list, &mw, &mh);
265 if (mh < 208) mh = 208;
266 e_widget_size_min_set(list, 208, mh);
267
268 e_gadcon_popup_content_set(inst->popup, list);
269 e_comp_object_util_autoclose(inst->popup->comp_object,
270 _popup_comp_del_cb, NULL, inst);
271 e_gadcon_popup_show(inst->popup);
272 e_object_data_set(E_OBJECT(inst->popup), inst);
273 E_OBJECT_DEL_SET(inst->popup, _popup_del_cb);
274}
275
276static void
277_menu_cb(void *data, E_Menu *menu EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
278{
279 _epulse_exec_cb(data, NULL);
280}
281
282static void
283_menu_new(Instance *inst, Evas_Event_Mouse_Down *ev)
284{
285 E_Zone *zone;
286 E_Menu *m;
287 E_Menu_Item *mi;
288 int x, y;
289
290 zone = e_util_zone_current_get(e_manager_current_get());
291
292 m = e_menu_new();
293
294 mi = e_menu_item_new(m);
295 e_menu_item_label_set(mi, "Advanced");
296 e_util_menu_item_theme_icon_set(mi, "configure");
297 e_menu_item_callback_set(mi, _menu_cb, inst);
298
299 m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0);
300 e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
301 e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
302 1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
303 evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button,
304 EVAS_BUTTON_NONE, ev->timestamp, NULL);
305}
306
307static void
308_mouse_down_cb(void *data, Evas *evas EINA_UNUSED,
309 Evas_Object *obj EINA_UNUSED, void *event)
310{
311 Instance *inst = data;
312 Evas_Event_Mouse_Down *ev = event;
313
314 if (ev->button == 1)
315 {
316 if (!inst->popup)
317 _popup_new(inst);
318 }
319 else if (ev->button == 3)
320 {
321 _menu_new(inst, ev);
322 }
323}
324
325/*
326 * Gadcon functions
327 */
328static E_Gadcon_Client *
329_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
330{
331 E_Gadcon_Client *gcc;
332 Instance *inst;
333
334 inst = E_NEW(Instance, 1);
335
336 inst->gadget = edje_object_add(gc->evas);
337 edje_object_file_set(inst->gadget, mixer_context->theme,
338 "e/modules/mixer/main");
339
340 gcc = e_gadcon_client_new(gc, name, id, style, inst->gadget);
341 gcc->data = inst;
342 inst->gcc = gcc;
343
344 evas_object_event_callback_add(inst->gadget, EVAS_CALLBACK_MOUSE_DOWN,
345 _mouse_down_cb, inst);
346 mixer_context->instances = eina_list_append(mixer_context->instances, inst);
347
348 return gcc;
349}
350
351static void
352_gc_shutdown(E_Gadcon_Client *gcc)
353{
354 Instance *inst;
355
356 inst = gcc->data;
357 evas_object_del(inst->gadget);
358 mixer_context->instances = eina_list_remove(mixer_context->instances, inst);
359 free(inst);
360}
361
362static void
363_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient)
364{
365 Instance *inst;
366 int w, h;
367
368 inst = gcc->data;
369 if (orient != E_GADCON_ORIENT_LAST)
370 inst->orient = orient;
371
372 switch (inst->orient)
373 {
374 case E_GADCON_ORIENT_VERT:
375 case E_GADCON_ORIENT_LEFT:
376 case E_GADCON_ORIENT_RIGHT:
377 case E_GADCON_ORIENT_CORNER_LT:
378 case E_GADCON_ORIENT_CORNER_RT:
379 case E_GADCON_ORIENT_CORNER_LB:
380 case E_GADCON_ORIENT_CORNER_RB:
381 w = 16;
382 h = 16;
383 default:
384 break;
385 }
386
387 e_gadcon_client_aspect_set(gcc, w, h);
388 e_gadcon_client_min_size_set(gcc, w, h);
389}
390
391static const char *
392_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
393{
394 return "Pulse Mixer";
395}
396
397static Evas_Object *
398_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas)
399{
400 Evas_Object *o;
401 char buf[4096] = { 0 };
402
403 o = edje_object_add(evas);
404 snprintf(buf, sizeof(buf), "%s/mixer.edj",
405 e_module_dir_get(mixer_context->module));
406 edje_object_file_set(o, buf, "icon");
407
408 return o;
409}
410
411static const char *
412_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED)
413{
414 return _gadcon_class.name;
415}
416
417static Eina_Bool
418_sink_default_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
419 void *info)
420{
421 Epulse_Event *ev = info;
422 Eina_List *l;
423 Sink *s;
424
425 EINA_LIST_FOREACH(mixer_context->sinks, l, s)
426 {
427 if (s->index == ev->index)
428 {
429 mixer_context->sink_default = s;
430 goto end;
431 }
432 }
433
434 s = malloc(sizeof(*s));
435 s->index = ev->index;
436 s->volume = ev->volume;
437 s->name = strdup(ev->name);
438 s->mute = ev->mute;
439
440 mixer_context->sinks = eina_list_append(mixer_context->sinks, s);
441 mixer_context->sink_default = s;
442
443 end:
444 _mixer_gadget_update();
445 return ECORE_CALLBACK_DONE;
446}
447
448static Eina_Bool
449_sink_changed_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
450 void *info)
451{
452 Epulse_Event *ev = info;
453 Eina_List *l;
454 Sink *s;
455
456 EINA_LIST_FOREACH(mixer_context->sinks, l, s)
457 {
458 if (ev->index == s->index)
459 {
460 s->mute = ev->mute;
461 s->volume = ev->volume;
462 if (ev->index == mixer_context->sink_default->index)
463 _mixer_gadget_update();
464 }
465 }
466
467 return ECORE_CALLBACK_DONE;
468}
469
470static Eina_Bool
471_sink_added_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
472 void *info)
473{
474 Epulse_Event *ev = info;
475 Eina_List *l;
476 Sink *s;
477
478 EINA_LIST_FOREACH(mixer_context->sinks, l, s)
479 if (s->index == ev->index)
480 return ECORE_CALLBACK_DONE;
481
482 s = malloc(sizeof(*s));
483 s->index = ev->index;
484 s->volume = ev->volume;
485 s->name = strdup(ev->name);
486 s->mute = ev->mute;
487
488 mixer_context->sinks = eina_list_append(mixer_context->sinks, s);
489 return ECORE_CALLBACK_DONE;
490}
491
492static Eina_Bool
493_sink_removed_cb(void *data EINA_UNUSED, int type EINA_UNUSED,
494 void *info)
495{
496 Epulse_Event *ev = info;
497 Eina_List *l, *ll;
498 Sink *s;
499
500 EINA_LIST_FOREACH_SAFE(mixer_context->sinks, l, ll, s)
501 {
502 if (ev->index == s->index)
503 {
504 free(s->name);
505 free(s);
506 mixer_context->sinks =
507 eina_list_remove_list(mixer_context->sinks, l);
508 }
509 }
510
511 if (ev->index == mixer_context->sink_default->index)
512 {
513 s = mixer_context->sinks->data;
514 mixer_context->sink_default = s;
515 _mixer_gadget_update();
516 }
517
518 return ECORE_CALLBACK_DONE;
519}
520
521EAPI void *
522e_modapi_init(E_Module *m)
523{
524 char buf[4096];
525
526 EINA_SAFETY_ON_FALSE_RETURN_VAL(epulse_common_init("epulse_mod"),
527 NULL);
528 EINA_SAFETY_ON_FALSE_RETURN_VAL(epulse_init() > 0, NULL);
529 if (!mixer_context)
530 {
531 mixer_context = E_NEW(Context, 1);
532
533 mixer_context->sink_default_handler =
534 ecore_event_handler_add(SINK_DEFAULT, _sink_default_cb, NULL);
535 mixer_context->sink_changed_handler =
536 ecore_event_handler_add(SINK_CHANGED, _sink_changed_cb, NULL);
537 mixer_context->sink_added_handler =
538 ecore_event_handler_add(SINK_ADDED, _sink_added_cb, NULL);
539 mixer_context->sink_removed_handler =
540 ecore_event_handler_add(SINK_REMOVED, _sink_removed_cb, NULL);
541 mixer_context->module = m;
542 snprintf(buf, sizeof(buf), "%s/mixer.edj",
543 e_module_dir_get(mixer_context->module));
544 mixer_context->theme = strdup(buf);
545 }
546
547 e_gadcon_provider_register(&_gadcon_class);
548
549 return m;
550}
551
552EAPI int
553e_modapi_shutdown(E_Module *m EINA_UNUSED)
554{
555 Sink *s;
556 e_gadcon_provider_unregister((const E_Gadcon_Client_Class *)&_gadcon_class);
557
558 if (!mixer_context)
559 {
560 if (mixer_context->theme)
561 free(mixer_context->theme);
562
563 ecore_event_handler_del(mixer_context->sink_default_handler);
564 ecore_event_handler_del(mixer_context->sink_changed_handler);
565 ecore_event_handler_del(mixer_context->sink_added_handler);
566 ecore_event_handler_del(mixer_context->sink_removed_handler);
567
568 EINA_LIST_FREE(mixer_context->sinks, s)
569 {
570 free(s->name);
571 free(s);
572 }
573
574 E_FREE(mixer_context);
575 }
576
577 epulse_common_shutdown();
578 epulse_shutdown();
579 return 1;
580}
581
582EAPI int
583e_modapi_save(E_Module *m EINA_UNUSED)
584{
585 return 1;
586}
diff --git a/src/module/e_mod_main.h b/src/module/e_mod_main.h
new file mode 100644
index 0000000..01b0f35
--- /dev/null
+++ b/src/module/e_mod_main.h
@@ -0,0 +1,12 @@
1#ifndef _E_MOD_MAIN_H_
2#define _E_MOD_MAIN_H_
3
4#define CONFIG_VERSION 1
5
6EAPI extern E_Module_Api e_modapi;
7
8EAPI void *e_modapi_init(E_Module *m);
9EAPI int e_modapi_shutdown(E_Module *m);
10EAPI int e_modapi_save(E_Module *m);
11
12#endif /* _E_MOD_MAIN_H_ */