update for beta release
[framework/uifw/e17.git] / src / modules / bluez / e_mod_main.c
1 /*
2  * TODO:
3  *
4  *   HIGH:
5  *
6  *     - check why return NULL from method call triggers cancel error
7  *       after timeout.
8  *     - find out why alias == address in _bluez_request_pincode_cb
9  *     - more complete agent support (handle requests from devices)
10  *     - handle device-disappeared events
11  *     - icon with device state (trusted, connected, paired)
12  *
13  *   LOW:
14  *
15  *     - configure (probably module) timeout to trigger automatic rescan.
16  *     - gadgets to show different adapters (see mixer module configuration)
17  *     - module to choose the default adapter (see mixer module configuration)
18  *     - icon with device class
19  */
20 #include "e.h"
21 #include "e_mod_main.h"
22
23 static E_Module *bluez_mod = NULL;
24 static char tmpbuf[PATH_MAX]; /* general purpose buffer, just use immediately */
25
26 static const char _e_bluez_agent_path[] = "/org/enlightenment/bluez/Agent";
27 const char _e_bluez_name[] = "bluez";
28 const char _e_bluez_Name[] = "Bluetooth Manager";
29 int _e_bluez_log_dom = -1;
30
31 static void _bluez_gadget_update(E_Bluez_Instance *inst);
32 static void _bluez_tip_update(E_Bluez_Instance *inst);
33 static void _bluez_popup_update(E_Bluez_Instance *inst);
34
35 struct bluez_pincode_data
36 {
37    void                    (*cb)(struct bluez_pincode_data *d);
38    DBusMessage            *msg;
39    E_Bluez_Module_Context *ctxt;
40    char                   *pincode;
41    const char             *alias;
42    E_Dialog               *dia;
43    Evas_Object            *entry;
44    Eina_Bool               canceled;
45 };
46
47 const char *
48 e_bluez_theme_path(void)
49 {
50 #define TF "/e-module-bluez.edj"
51    size_t dirlen;
52
53    dirlen = strlen(bluez_mod->dir);
54    if (dirlen >= sizeof(tmpbuf) - sizeof(TF))
55      return NULL;
56
57    memcpy(tmpbuf, bluez_mod->dir, dirlen);
58    memcpy(tmpbuf + dirlen, TF, sizeof(TF));
59
60    return tmpbuf;
61 #undef TF
62 }
63
64 static void
65 _bluez_devices_clear(E_Bluez_Instance *inst)
66 {
67    E_Bluez_Instance_Device *d;
68    EINA_LIST_FREE(inst->devices, d)
69      {
70         eina_stringshare_del(d->address);
71         eina_stringshare_del(d->alias);
72         free(d);
73      }
74    inst->address = NULL;
75    inst->alias = NULL;
76 }
77
78 static void
79 _bluez_discovery_cb(void            *data,
80                     DBusMessage *msg __UNUSED__,
81                     DBusError       *error)
82 {
83    E_Bluez_Instance *inst = data;
84    char *label;
85
86    if (error && dbus_error_is_set(error))
87      {
88         _bluez_dbus_error_show(_("Cannot change adapter's discovery."), error);
89         dbus_error_free(error);
90         return;
91      }
92
93    inst->discovering = !inst->discovering;
94
95    label = !inst->discovering ? _("Start Scan") : _("Stop Scan");
96    e_widget_button_label_set(inst->ui.button, label);
97 }
98
99 static void
100 _bluez_create_paired_device_cb(void            *data,
101                                DBusMessage *msg __UNUSED__,
102                                DBusError       *error)
103 {
104    const char *alias = data;
105
106    if (error && dbus_error_is_set(error))
107      {
108         if (strcmp(error->name, "org.bluez.Error.AlreadyExists") != 0)
109           _bluez_dbus_error_show(_("Cannot pair with device."), error);
110         dbus_error_free(error);
111         eina_stringshare_del(alias);
112         return;
113      }
114
115    e_util_dialog_show
116      (_("Bluetooth Manager"), _("Device '%s' successfully paired."), alias);
117    eina_stringshare_del(alias);
118 }
119
120 static void
121 _bluez_toggle_powered_cb(void            *data,
122                          DBusMessage *msg __UNUSED__,
123                          DBusError       *error)
124 {
125    E_Bluez_Instance *inst = data;
126
127    if ((!error) || (!dbus_error_is_set(error)))
128      {
129         inst->powered_pending = EINA_FALSE;
130         inst->powered = !inst->powered;
131
132         if (!inst->powered)
133           {
134              _bluez_devices_clear(inst);
135
136              if (inst->popup)
137                _bluez_popup_update(inst);
138           }
139
140         _bluez_gadget_update(inst);
141         return;
142      }
143
144    _bluez_dbus_error_show(_("Cannot toggle adapter's powered."), error);
145    dbus_error_free(error);
146 }
147
148 void
149 _bluez_toggle_powered(E_Bluez_Instance *inst)
150 {
151    Eina_Bool powered;
152
153    if ((!inst) || (!inst->ctxt->has_manager))
154      {
155         _bluez_operation_error_show(_("BlueZ Daemon is not running."));
156         return;
157      }
158
159    if (!inst->adapter)
160      {
161         _bluez_operation_error_show(_("No bluetooth adapter."));
162         return;
163      }
164
165    if (!e_bluez_adapter_powered_get(inst->adapter, &powered))
166      {
167         _bluez_operation_error_show(_("Query adapter's powered."));
168         return;
169      }
170
171    powered = !powered;
172
173    if (!e_bluez_adapter_powered_set
174          (inst->adapter, powered, _bluez_toggle_powered_cb, inst))
175      {
176         _bluez_operation_error_show(_("Query adapter's powered."));
177         return;
178      }
179 }
180
181 static void
182 _bluez_cb_toggle_powered(E_Object *obj      __UNUSED__,
183                          const char *params __UNUSED__)
184 {
185    E_Bluez_Module_Context *ctxt;
186    const Eina_List *l;
187    E_Bluez_Instance *inst;
188
189    if (!bluez_mod)
190      return;
191
192    ctxt = bluez_mod->data;
193    EINA_LIST_FOREACH(ctxt->instances, l, inst)
194      if (inst->adapter) _bluez_toggle_powered(inst);
195 }
196
197 static void _bluez_popup_del(E_Bluez_Instance *inst);
198
199 static Eina_Bool
200 _bluez_popup_input_window_mouse_up_cb(void    *data,
201                                       int type __UNUSED__,
202                                       void    *event)
203 {
204    Ecore_Event_Mouse_Button *ev = event;
205    E_Bluez_Instance *inst = data;
206
207    if (ev->window != inst->ui.input.win)
208      return ECORE_CALLBACK_PASS_ON;
209
210    _bluez_popup_del(inst);
211
212    return ECORE_CALLBACK_PASS_ON;
213 }
214
215 static Eina_Bool
216 _bluez_popup_input_window_key_down_cb(void    *data,
217                                       int type __UNUSED__,
218                                       void    *event)
219 {
220    Ecore_Event_Key *ev = event;
221    E_Bluez_Instance *inst = data;
222    const char *keysym;
223
224    if (ev->window != inst->ui.input.win)
225      return ECORE_CALLBACK_PASS_ON;
226
227    keysym = ev->key;
228    if (strcmp(keysym, "Escape") == 0)
229      _bluez_popup_del(inst);
230
231    return ECORE_CALLBACK_PASS_ON;
232 }
233
234 static void
235 _bluez_popup_input_window_destroy(E_Bluez_Instance *inst)
236 {
237    ecore_x_window_free(inst->ui.input.win);
238    inst->ui.input.win = 0;
239
240    ecore_event_handler_del(inst->ui.input.mouse_up);
241    inst->ui.input.mouse_up = NULL;
242
243    ecore_event_handler_del(inst->ui.input.key_down);
244    inst->ui.input.key_down = NULL;
245 }
246
247 static void
248 _bluez_popup_input_window_create(E_Bluez_Instance *inst)
249 {
250    Ecore_X_Window_Configure_Mask mask;
251    Ecore_X_Window w, popup_w;
252    E_Manager *man;
253
254    man = e_manager_current_get();
255
256    w = ecore_x_window_input_new(man->root, 0, 0, man->w, man->h);
257    mask = (ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE |
258            ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING);
259    popup_w = inst->popup->win->evas_win;
260    ecore_x_window_configure(w, mask, 0, 0, 0, 0, 0, popup_w,
261                             ECORE_X_WINDOW_STACK_BELOW);
262    ecore_x_window_show(w);
263
264    inst->ui.input.mouse_up =
265      ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP,
266                              _bluez_popup_input_window_mouse_up_cb, inst);
267
268    inst->ui.input.key_down =
269      ecore_event_handler_add(ECORE_EVENT_KEY_DOWN,
270                              _bluez_popup_input_window_key_down_cb, inst);
271
272    inst->ui.input.win = w;
273 }
274
275 static void
276 _bluez_popup_cb_powered_changed(void        *data,
277                                 Evas_Object *obj)
278 {
279    E_Bluez_Instance *inst = data;
280    E_Bluez_Module_Context *ctxt = inst->ctxt;
281    Eina_Bool powered = e_widget_check_checked_get(obj);
282
283    if ((!ctxt) || (!ctxt->has_manager))
284      {
285         _bluez_operation_error_show(_("BlueZ Daemon is not running."));
286         return;
287      }
288
289    if (!inst->adapter)
290      {
291         _bluez_operation_error_show(_("No bluetooth adapter."));
292         return;
293      }
294
295    if (!e_bluez_adapter_powered_set
296          (inst->adapter, powered, _bluez_toggle_powered_cb, inst))
297      {
298         _bluez_operation_error_show
299           (_("Cannot toggle adapter's powered."));
300         return;
301      }
302
303    inst->powered_pending = EINA_TRUE;
304 }
305
306 static void
307 _bluez_pincode_ask_cb(struct bluez_pincode_data *d)
308 {
309    DBusMessage *reply;
310
311    if (!d->pincode)
312      {
313         e_util_dialog_show(_("Bluetooth Manager"), _("Invalid Pin Code."));
314         return;
315      }
316
317    reply = dbus_message_new_method_return(d->msg);
318    dbus_message_append_args
319      (reply, DBUS_TYPE_STRING, &d->pincode, DBUS_TYPE_INVALID);
320
321    dbus_message_set_no_reply(reply, EINA_TRUE);
322    e_dbus_message_send(d->ctxt->agent.conn, reply, NULL, -1, NULL);
323 }
324
325 static void
326 bluez_pincode_ask_ok(void     *data,
327                      E_Dialog *dia)
328 {
329    struct bluez_pincode_data *d = data;
330    d->canceled = EINA_FALSE;
331    e_object_del(E_OBJECT(dia));
332 }
333
334 static void
335 bluez_pincode_ask_cancel(void     *data,
336                          E_Dialog *dia)
337 {
338    struct bluez_pincode_data *d = data;
339    d->canceled = EINA_TRUE;
340    e_object_del(E_OBJECT(dia));
341 }
342
343 static void
344 bluez_pincode_ask_del(void *data)
345 {
346    E_Dialog *dia = data;
347    struct bluez_pincode_data *d = e_object_data_get(E_OBJECT(dia));
348
349    if (!d->canceled)
350      d->cb(d);
351
352    d->ctxt->agent.pending = eina_list_remove(d->ctxt->agent.pending, dia);
353
354    free(d->pincode);
355    dbus_message_unref(d->msg);
356    eina_stringshare_del(d->alias);
357    E_FREE(d);
358 }
359
360 static void
361 bluez_pincode_ask_key_down(void          *data,
362                            Evas *e        __UNUSED__,
363                            Evas_Object *o __UNUSED__,
364                            void          *event)
365 {
366    Evas_Event_Key_Down *ev = event;
367    struct bluez_pincode_data *d = data;
368
369    if (strcmp(ev->keyname, "Return") == 0)
370      bluez_pincode_ask_ok(d, d->dia);
371    else if (strcmp(ev->keyname, "Escape") == 0)
372      bluez_pincode_ask_cancel(d, d->dia);
373 }
374
375 static void
376 bluez_pincode_ask(void                    (*cb)(struct bluez_pincode_data *),
377                   DBusMessage            *msg,
378                   const char             *alias,
379                   E_Bluez_Module_Context *ctxt)
380 {
381    struct bluez_pincode_data *d;
382    Evas_Object *list, *o;
383    Evas *evas;
384    char buf[512];
385    int mw, mh;
386
387    if (!cb)
388      return;
389
390    d = E_NEW(struct bluez_pincode_data, 1);
391    if (!d)
392      return;
393
394    d->cb = cb;
395    d->ctxt = ctxt;
396    d->alias = eina_stringshare_add(alias);
397    d->msg = dbus_message_ref(msg);
398    d->canceled = EINA_TRUE; /* closing the dialog defaults to cancel */
399    d->dia = e_dialog_new(NULL, "E", "bluez_ask_pincode");
400
401    snprintf(buf, sizeof(buf), _("Pairing with device '%s'"), alias);
402    e_dialog_title_set(d->dia, buf);
403    e_dialog_icon_set(d->dia, "dialog-ask", 32);
404    e_dialog_border_icon_set(d->dia, "dialog-ask");
405
406    evas = d->dia->win->evas;
407
408    list = e_widget_list_add(evas, 0, 0);
409
410    o = edje_object_add(evas);
411    e_theme_edje_object_set(o, "base/theme/dialog",
412                            "e/widgets/dialog/text");
413    snprintf(buf, sizeof(buf),
414             _("Enter the PIN code: "));
415    edje_object_part_text_set(o, "e.textblock.message", buf);
416    edje_object_size_min_calc(o, &mw, &mh);
417    evas_object_size_hint_min_set(o, mw, mh);
418    evas_object_resize(o, mw, mh);
419    evas_object_show(o);
420    e_widget_list_object_append(list, o, 1, 1, 0.5);
421
422    d->entry = o = e_widget_entry_add(evas, &d->pincode, NULL, NULL, NULL);
423    e_widget_entry_password_set(o, 0);
424    evas_object_show(o);
425    e_widget_list_object_append(list, o, 1, 0, 0.0);
426
427    e_widget_size_min_get(list, &mw, &mh);
428    if (mw < 200)
429      mw = 200;
430    if (mh < 60)
431      mh = 60;
432    e_dialog_content_set(d->dia, list, mw, mh);
433
434    e_dialog_button_add
435      (d->dia, _("Ok"), NULL, bluez_pincode_ask_ok, d);
436    e_dialog_button_add
437      (d->dia, _("Cancel"), NULL, bluez_pincode_ask_cancel, d);
438
439    evas_object_event_callback_add
440      (d->dia->bg_object, EVAS_CALLBACK_KEY_DOWN,
441      bluez_pincode_ask_key_down, d);
442
443    e_object_del_attach_func_set
444      (E_OBJECT(d->dia), bluez_pincode_ask_del);
445    e_object_data_set(E_OBJECT(d->dia), d);
446
447    e_dialog_button_focus_num(d->dia, 0);
448    e_widget_focus_set(d->entry, 1);
449
450    e_win_centered_set(d->dia->win, 1);
451    e_dialog_show(d->dia);
452
453    ctxt->agent.pending = eina_list_append(ctxt->agent.pending, d->dia);
454 }
455
456 static DBusMessage *
457 _bluez_request_pincode_cb(E_DBus_Object *obj,
458                           DBusMessage   *msg)
459 {
460    E_Bluez_Module_Context *ctxt = e_dbus_object_data_get(obj);
461    E_Bluez_Element *element;
462    const char *path;
463    const char *alias;
464
465    // TODO: seems that returning NULL is causing pin code rquest to be canceled!
466
467    if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
468                              DBUS_TYPE_INVALID) == FALSE)
469      return NULL;
470
471    element = e_bluez_device_get(path);
472    if (!element)
473      alias = path;
474    else
475      {
476         if (!e_bluez_device_alias_get(element, &alias))
477           {
478              if (!e_bluez_device_name_get(element, &alias))
479                alias = path;
480           }
481      }
482    // TODO: find out why alias == address, then remove debug:
483    fprintf(stderr, ">>> request pin code of '%s' (%s)\n", alias, path);
484    bluez_pincode_ask(_bluez_pincode_ask_cb, msg, alias, ctxt);
485    return NULL;
486 }
487
488 static void
489 _bluez_popup_cb_scan(void       *data,
490                      void *data2 __UNUSED__)
491 {
492    E_Bluez_Instance *inst = data;
493    int ret;
494
495    if (!inst->adapter)
496      ret = 0;
497    else if (inst->discovering)
498      ret = e_bluez_adapter_stop_discovery
499          (inst->adapter, _bluez_discovery_cb, inst);
500    else
501      {
502         inst->last_scan = ecore_loop_time_get();
503
504         _bluez_devices_clear(inst);
505
506         ret = e_bluez_adapter_start_discovery
507             (inst->adapter, _bluez_discovery_cb, inst);
508
509         _bluez_popup_update(inst);
510      }
511
512    if (!ret)
513      ERR("Failed on discovery procedure");
514 }
515
516 static void
517 _bluez_popup_cb_controls(void       *data,
518                          void *data2 __UNUSED__)
519 {
520    E_Bluez_Instance *inst = data;
521    if (inst->popup)
522      _bluez_popup_del(inst);
523    if (inst->conf_dialog)
524      return;
525    if (!inst->adapter)
526      return;
527    inst->conf_dialog = e_bluez_config_dialog_new(NULL, inst);
528 }
529
530 static void
531 _bluez_popup_device_selected(void *data)
532 {
533    E_Bluez_Instance *inst = data;
534    const char *address = inst->address;
535    const char *alias;
536    const char *cap = "DisplayYesNo";
537    const E_Bluez_Instance_Device *d;
538    const Eina_List *l;
539
540    if (inst->popup)
541      _bluez_popup_del(inst);
542
543    if (!address)
544      {
545         ERR("no device selected for pairing.");
546         return;
547      }
548
549    inst->alias = address;
550    EINA_LIST_FOREACH(inst->devices, l, d)
551      {
552         if (address == d->alias)
553           {
554              inst->alias = d->alias;
555              break;
556           }
557      }
558
559    if (!inst->alias)
560      {
561         ERR("device %s does not have an alias.", address);
562         return;
563      }
564
565    alias = eina_stringshare_ref(inst->alias);
566    if (!e_bluez_adapter_create_paired_device
567          (inst->adapter, _e_bluez_agent_path, cap, address,
568          _bluez_create_paired_device_cb, alias))
569      {
570         eina_stringshare_del(alias);
571         return;
572      }
573 }
574
575 static Eina_Bool
576 _bluez_event_devicefound(void       *data,
577                          int type    __UNUSED__,
578                          void *event)
579 {
580    E_Bluez_Module_Context *ctxt = data;
581    E_Bluez_Device_Found *device = event;
582    E_Bluez_Instance *inst;
583    const Eina_List *l_inst;
584    const char *alias;
585
586    // TODO: get properties such as paired, connected, trusted, class, icon...
587    // TODO: check if the adapter contains device->name and if so get path.
588
589    alias = e_bluez_devicefound_alias_get(device);
590
591    EINA_LIST_FOREACH(ctxt->instances, l_inst, inst)
592      {
593         const Eina_List *l_dev;
594         E_Bluez_Instance_Device *dev;
595         Eina_Bool found = EINA_FALSE;
596
597         if (inst->adapter != device->adapter) continue;
598
599         EINA_LIST_FOREACH(inst->devices, l_dev, dev)
600           {
601              if (dev->address == device->name)
602                {
603                   found = EINA_TRUE;
604                   break;
605                }
606           }
607
608         if (found) continue;
609
610         dev = malloc(sizeof(E_Bluez_Instance_Device));
611         if (!dev) continue;
612
613         dev->address = eina_stringshare_ref(device->name);
614         dev->alias = eina_stringshare_ref(alias);
615
616         inst->devices = eina_list_append(inst->devices, dev);
617
618         if (inst->ui.list)
619           {
620              e_widget_ilist_append
621                (inst->ui.list, NULL, dev->alias,
622                _bluez_popup_device_selected, inst, dev->address);
623              e_widget_ilist_go(inst->ui.list);
624           }
625      }
626
627    return 1;
628 }
629
630 static void
631 _bluez_popup_update(E_Bluez_Instance *inst)
632 {
633    Evas_Object *list = inst->ui.list;
634    int selected;
635    const char *label;
636    E_Bluez_Instance_Device *d;
637    Eina_List *l;
638
639    /* TODO: replace this with a scroller + list of edje
640     * objects that are more full of features
641     */
642    selected = e_widget_ilist_selected_get(list);
643    e_widget_ilist_freeze(list);
644    e_widget_ilist_clear(list);
645
646    EINA_LIST_FOREACH(inst->devices, l, d)
647      {
648         e_widget_ilist_append
649           (inst->ui.list, NULL, d->alias,
650           _bluez_popup_device_selected, inst, d->address);
651      }
652
653    if (selected >= 0)
654      {
655         inst->first_selection = EINA_TRUE;
656         e_widget_ilist_selected_set(list, selected);
657      }
658    else
659      inst->first_selection = EINA_FALSE;
660
661    e_widget_ilist_go(list);
662
663    e_widget_check_checked_set(inst->ui.powered, inst->powered);
664    label = inst->discovering ? _("Stop Scan") : _("Start Scan");
665    e_widget_button_label_set(inst->ui.button, label);
666    e_widget_disabled_set(inst->ui.button, !inst->powered);
667 }
668
669 static void
670 _bluez_popup_del(E_Bluez_Instance *inst)
671 {
672    _bluez_popup_input_window_destroy(inst);
673    e_object_del(E_OBJECT(inst->popup));
674    inst->popup = NULL;
675 }
676
677 static void
678 _bluez_popup_new(E_Bluez_Instance *inst)
679 {
680    Evas_Object *ol;
681    Evas *evas;
682    Evas_Coord mw, mh;
683    const char *label;
684    Eina_Bool b, needs_scan = EINA_FALSE;
685
686    if (inst->popup)
687      {
688         e_gadcon_popup_show(inst->popup);
689         return;
690      }
691
692    if (!inst->adapter)
693      {
694         _bluez_operation_error_show(_("No bluetooth adapter."));
695         return;
696      }
697
698    if (!e_bluez_adapter_discovering_get(inst->adapter, &b))
699      {
700         _bluez_operation_error_show(_("Can't get Discovering property"));
701         return;
702      }
703    inst->discovering = b;
704    // maybe auto-scan if did not in the last 30 minutes?
705    // seems scan will hurt things like bluetooth audio playback, so don't do it
706    if ((!inst->discovering) && (inst->last_scan <= 0.0) && (inst->ui.powered))
707      {
708         label = _("Stop Scan");
709         needs_scan = EINA_TRUE;
710      }
711    else
712      label = inst->discovering ? _("Stop Scan") : _("Start Scan");
713
714    inst->popup = e_gadcon_popup_new(inst->gcc);
715    evas = inst->popup->win->evas;
716
717    ol = e_widget_list_add(evas, 0, 0);
718
719    // TODO: get this size from edj
720    inst->ui.list = e_widget_ilist_add(evas, 32, 32, &inst->address);
721    e_widget_size_min_set(inst->ui.list, 180, 100);
722    e_widget_list_object_append(ol, inst->ui.list, 1, 1, 0.5);
723
724    inst->powered = inst->powered;
725    inst->ui.powered = e_widget_check_add(evas, _("Powered"), &inst->powered);
726    e_widget_on_change_hook_set
727      (inst->ui.powered, _bluez_popup_cb_powered_changed, inst);
728    e_widget_list_object_append(ol, inst->ui.powered, 1, 0, 0.5);
729
730    inst->ui.button = e_widget_button_add
731        (evas, label, NULL, _bluez_popup_cb_scan, inst, NULL);
732    e_widget_list_object_append(ol, inst->ui.button, 1, 0, 0.5);
733
734    inst->ui.control = e_widget_button_add
735        (evas, _("Controls"), NULL, _bluez_popup_cb_controls, inst, NULL);
736    e_widget_list_object_append(ol, inst->ui.control, 1, 0, 0.5);
737
738    _bluez_popup_update(inst);
739
740    e_widget_size_min_get(ol, &mw, &mh);
741    if (mh < 200) mh = 200;
742    if (mw < 200) mw = 200;
743    e_widget_size_min_set(ol, mw, mh);
744
745    e_gadcon_popup_content_set(inst->popup, ol);
746    e_gadcon_popup_show(inst->popup);
747    _bluez_popup_input_window_create(inst);
748
749    if (needs_scan) _bluez_popup_cb_scan(inst, NULL);
750 }
751
752 static void
753 _bluez_menu_cb_post(void        *data,
754                     E_Menu *menu __UNUSED__)
755 {
756    E_Bluez_Instance *inst = data;
757    if ((!inst) || (!inst->menu))
758      return;
759    if (inst->menu)
760      {
761         e_object_del(E_OBJECT(inst->menu));
762         inst->menu = NULL;
763      }
764 }
765
766 static void
767 _bluez_menu_cb_cfg(void           *data,
768                    E_Menu *menu    __UNUSED__,
769                    E_Menu_Item *mi __UNUSED__)
770 {
771    E_Bluez_Instance *inst = data;
772    if (inst->popup)
773      _bluez_popup_del(inst);
774    if (inst->conf_dialog)
775      return;
776    if (!inst->adapter)
777      return;
778    inst->conf_dialog = e_bluez_config_dialog_new(NULL, inst);
779 }
780
781 static void
782 _bluez_menu_new(E_Bluez_Instance      *inst,
783                 Evas_Event_Mouse_Down *ev)
784 {
785    E_Zone *zone;
786    E_Menu *m;
787    E_Menu_Item *mi;
788    int x, y;
789
790    zone = e_util_zone_current_get(e_manager_current_get());
791
792    m = e_menu_new();
793    mi = e_menu_item_new(m);
794    e_menu_item_label_set(mi, _("Settings"));
795    e_util_menu_item_theme_icon_set(mi, "configure");
796    e_menu_item_callback_set(mi, _bluez_menu_cb_cfg, inst);
797
798    m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0);
799    e_menu_post_deactivate_callback_set(m, _bluez_menu_cb_post, inst);
800    inst->menu = m;
801
802    e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
803    e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
804                          1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
805    evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button,
806                             EVAS_BUTTON_NONE, ev->timestamp, NULL);
807 }
808
809 static void
810 _bluez_tip_new(E_Bluez_Instance *inst)
811 {
812    Evas *e;
813
814    inst->tip = e_gadcon_popup_new(inst->gcc);
815    if (!inst->tip) return;
816
817    e = inst->tip->win->evas;
818
819    inst->o_tip = edje_object_add(e);
820    e_theme_edje_object_set(inst->o_tip, "base/theme/modules/bluez/tip",
821                            "e/modules/bluez/tip");
822
823    _bluez_tip_update(inst);
824
825    e_gadcon_popup_content_set(inst->tip, inst->o_tip);
826    e_gadcon_popup_show(inst->tip);
827 }
828
829 static void
830 _bluez_tip_del(E_Bluez_Instance *inst)
831 {
832    evas_object_del(inst->o_tip);
833    e_object_del(E_OBJECT(inst->tip));
834    inst->tip = NULL;
835    inst->o_tip = NULL;
836 }
837
838 static void
839 _bluez_cb_mouse_down(void            *data,
840                      Evas *evas       __UNUSED__,
841                      Evas_Object *obj __UNUSED__,
842                      void            *event)
843 {
844    E_Bluez_Instance *inst;
845    Evas_Event_Mouse_Down *ev;
846
847    inst = data;
848    if (!inst)
849      return;
850
851    ev = event;
852    if (ev->button == 1)
853      {
854         if (!inst->popup)
855           _bluez_popup_new(inst);
856         else
857           _bluez_popup_del(inst);
858      }
859    else if (ev->button == 2)
860      _bluez_toggle_powered(inst);
861    else if ((ev->button == 3) && (!inst->menu))
862      _bluez_menu_new(inst, ev);
863 }
864
865 static void
866 _bluez_cb_mouse_in(void            *data,
867                    Evas *evas       __UNUSED__,
868                    Evas_Object *obj __UNUSED__,
869                    void *event      __UNUSED__)
870 {
871    E_Bluez_Instance *inst = data;
872
873    if (inst->tip)
874      return;
875
876    _bluez_tip_new(inst);
877 }
878
879 static void
880 _bluez_cb_mouse_out(void            *data,
881                     Evas *evas       __UNUSED__,
882                     Evas_Object *obj __UNUSED__,
883                     void *event      __UNUSED__)
884 {
885    E_Bluez_Instance *inst = data;
886
887    if (!inst->tip)
888      return;
889
890    _bluez_tip_del(inst);
891 }
892
893 static void
894 _bluez_edje_view_update(E_Bluez_Instance *inst,
895                         Evas_Object      *o)
896 {
897    E_Bluez_Module_Context *ctxt = inst->ctxt;
898    const char *name;
899
900    if (!ctxt->has_manager)
901      {
902         edje_object_part_text_set(o, "e.text.powered", "");
903         edje_object_part_text_set(o, "e.text.status", "");
904         edje_object_signal_emit(o, "e,changed,service,none", "e");
905         edje_object_part_text_set(o, "e.text.name", _("No Bluetooth daemon"));
906         edje_object_signal_emit(o, "e,changed,name", "e");
907         return;
908      }
909
910    if (!inst->adapter)
911      {
912         edje_object_part_text_set(o, "e.text.powered", "");
913         edje_object_part_text_set(o, "e.text.status", "");
914         edje_object_signal_emit(o, "e,changed,off", "e");
915         edje_object_part_text_set(o, "e.text.name", _("No Bluetooth adapter"));
916         edje_object_signal_emit(o, "e,changed,name", "e");
917         return;
918      }
919
920    if (!e_bluez_adapter_name_get(inst->adapter, &name))
921      name = "";
922    edje_object_part_text_set(o, "e.text.name", name);
923    edje_object_signal_emit(o, "e,changed,name", "e");
924
925    if (inst->powered)
926      {
927         if (inst->discoverable)
928           {
929              edje_object_signal_emit(o, "e,changed,powered", "e");
930              edje_object_part_text_set
931                (o, "e.text.status",
932                _("Bluetooth is powered and discoverable."));
933           }
934         else
935           {
936              edje_object_signal_emit(o, "e,changed,hidden", "e");
937              edje_object_part_text_set
938                (o, "e.text.status", _("Bluetooth is powered and hidden."));
939           }
940      }
941    else
942      {
943         edje_object_signal_emit(o, "e,changed,off", "e");
944         edje_object_part_text_set(o, "e.text.status", _("Bluetooth is off."));
945      }
946 }
947
948 static void
949 _bluez_tip_update(E_Bluez_Instance *inst)
950 {
951    _bluez_edje_view_update(inst, inst->o_tip);
952 }
953
954 static void
955 _bluez_gadget_update(E_Bluez_Instance *inst)
956 {
957    E_Bluez_Module_Context *ctxt = inst->ctxt;
958
959    if (inst->popup && ((!ctxt->has_manager) || (!inst->adapter)))
960      _bluez_popup_del(inst);
961
962    if (inst->popup)
963      _bluez_popup_update(inst);
964    if (inst->tip)
965      _bluez_tip_update(inst);
966
967    _bluez_edje_view_update(inst, inst->ui.gadget);
968 }
969
970 /* Gadcon Api Functions */
971
972 static E_Gadcon_Client *
973 _gc_init(E_Gadcon   *gc,
974          const char *name,
975          const char *id,
976          const char *style)
977 {
978    E_Bluez_Instance *inst;
979    E_Bluez_Module_Context *ctxt;
980
981    if (!bluez_mod)
982      return NULL;
983
984    ctxt = bluez_mod->data;
985
986    inst = E_NEW(E_Bluez_Instance, 1);
987    inst->ctxt = ctxt;
988    inst->ui.gadget = edje_object_add(gc->evas);
989    e_theme_edje_object_set(inst->ui.gadget, "base/theme/modules/bluez",
990                            "e/modules/bluez/main");
991
992    inst->gcc = e_gadcon_client_new(gc, name, id, style, inst->ui.gadget);
993    inst->gcc->data = inst;
994
995    evas_object_event_callback_add
996      (inst->ui.gadget, EVAS_CALLBACK_MOUSE_DOWN, _bluez_cb_mouse_down, inst);
997    evas_object_event_callback_add
998      (inst->ui.gadget, EVAS_CALLBACK_MOUSE_IN, _bluez_cb_mouse_in, inst);
999    evas_object_event_callback_add
1000      (inst->ui.gadget, EVAS_CALLBACK_MOUSE_OUT, _bluez_cb_mouse_out, inst);
1001
1002    // TODO: instead of getting the default adapter, get the adapter for
1003    // each instance. See the mixer module.
1004    if (ctxt->default_adapter)
1005      inst->adapter = e_bluez_adapter_get(ctxt->default_adapter);
1006    else
1007      inst->adapter = NULL;
1008
1009    if (inst->adapter)
1010      {
1011         Eina_Bool powered, discoverable, discovering;
1012
1013         if (e_bluez_adapter_powered_get(inst->adapter, &powered))
1014           inst->powered = powered;
1015
1016         if (e_bluez_adapter_discoverable_get(inst->adapter, &discoverable))
1017           inst->discoverable = discoverable;
1018
1019         if (e_bluez_adapter_discovering_get(inst->adapter, &discovering))
1020           inst->discovering = discovering;
1021      }
1022
1023    _bluez_gadget_update(inst);
1024
1025    ctxt->instances = eina_list_append(ctxt->instances, inst);
1026
1027    return inst->gcc;
1028 }
1029
1030 static void
1031 _gc_shutdown(E_Gadcon_Client *gcc)
1032 {
1033    E_Bluez_Module_Context *ctxt;
1034    E_Bluez_Instance *inst;
1035
1036    if (!bluez_mod)
1037      return;
1038
1039    ctxt = bluez_mod->data;
1040    if (!ctxt)
1041      return;
1042
1043    inst = gcc->data;
1044    if (!inst)
1045      return;
1046
1047    if (inst->menu)
1048      {
1049         e_menu_post_deactivate_callback_set(inst->menu, NULL, NULL);
1050         e_object_del(E_OBJECT(inst->menu));
1051      }
1052    evas_object_del(inst->ui.gadget);
1053
1054    _bluez_devices_clear(inst);
1055
1056    ctxt->instances = eina_list_remove(ctxt->instances, inst);
1057
1058    E_FREE(inst);
1059 }
1060
1061 static void
1062 _gc_orient(E_Gadcon_Client       *gcc,
1063            E_Gadcon_Orient orient __UNUSED__)
1064 {
1065    e_gadcon_client_aspect_set(gcc, 16, 16);
1066    e_gadcon_client_min_size_set(gcc, 16, 16);
1067 }
1068
1069 static const char *
1070 _gc_label(E_Gadcon_Client_Class *client_class __UNUSED__)
1071 {
1072    return _(_e_bluez_Name);
1073 }
1074
1075 static Evas_Object *
1076 _gc_icon(E_Gadcon_Client_Class *client_class __UNUSED__,
1077          Evas                               *evas)
1078 {
1079    Evas_Object *o;
1080
1081    o = edje_object_add(evas);
1082    edje_object_file_set(o, e_bluez_theme_path(), "icon");
1083    return o;
1084 }
1085
1086 static const char *
1087 _gc_id_new(E_Gadcon_Client_Class *client_class __UNUSED__)
1088 {
1089    E_Bluez_Module_Context *ctxt;
1090
1091    if (!bluez_mod)
1092      return NULL;
1093
1094    ctxt = bluez_mod->data;
1095    if (!ctxt)
1096      return NULL;
1097
1098    snprintf(tmpbuf, sizeof(tmpbuf), "bluez.%d",
1099             eina_list_count(ctxt->instances));
1100    return tmpbuf;
1101 }
1102
1103 static const E_Gadcon_Client_Class _gc_class =
1104 {
1105    GADCON_CLIENT_CLASS_VERSION, _e_bluez_name,
1106    {
1107       _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL,
1108       e_gadcon_site_is_not_toolbar
1109    },
1110    E_GADCON_CLIENT_STYLE_PLAIN
1111 };
1112
1113 EAPI E_Module_Api e_modapi = {E_MODULE_API_VERSION, _e_bluez_Name};
1114
1115 static const char _act_toggle_powered[] = "toggle_powered";
1116 static const char _lbl_toggle_powered[] = "Toggle Powered";
1117
1118 static void
1119 _bluez_actions_register(E_Bluez_Module_Context *ctxt)
1120 {
1121    ctxt->actions.toggle_powered = e_action_add(_act_toggle_powered);
1122    if (ctxt->actions.toggle_powered)
1123      {
1124         ctxt->actions.toggle_powered->func.go =
1125           _bluez_cb_toggle_powered;
1126         e_action_predef_name_set
1127           (_(_e_bluez_Name), _(_lbl_toggle_powered), _act_toggle_powered,
1128           NULL, NULL, 0);
1129      }
1130 }
1131
1132 static void
1133 _bluez_actions_unregister(E_Bluez_Module_Context *ctxt)
1134 {
1135    if (ctxt->actions.toggle_powered)
1136      {
1137         e_action_predef_name_del(_(_e_bluez_Name), _(_lbl_toggle_powered));
1138         e_action_del(_act_toggle_powered);
1139      }
1140 }
1141
1142 static Eina_Bool
1143 _bluez_manager_changed_do(void *data)
1144 {
1145    E_Bluez_Module_Context *ctxt = data;
1146
1147    //FIXME: reload the default adapter maybe?
1148
1149    ctxt->poller.manager_changed = NULL;
1150    return ECORE_CALLBACK_CANCEL;
1151 }
1152
1153 static void
1154 _bluez_manager_changed(void                          *data,
1155                        const E_Bluez_Element *element __UNUSED__)
1156 {
1157    E_Bluez_Module_Context *ctxt = data;
1158    if (ctxt->poller.manager_changed)
1159      ecore_poller_del(ctxt->poller.manager_changed);
1160    ctxt->poller.manager_changed = ecore_poller_add
1161        (ECORE_POLLER_CORE, 1, _bluez_manager_changed_do, ctxt);
1162 }
1163
1164 static void
1165 _properties_sync_callback(void            *data,
1166                           DBusMessage *msg __UNUSED__,
1167                           DBusError       *err)
1168 {
1169    E_Bluez_Instance *inst = data;
1170    Eina_Bool powered;
1171    Eina_Bool discoverable;
1172
1173    if (err && dbus_error_is_set(err))
1174      {
1175         dbus_error_free(err);
1176         return;
1177      }
1178
1179    if (!e_bluez_adapter_powered_get(inst->adapter, &powered))
1180      {
1181         _bluez_operation_error_show(_("Query adapter's powered."));
1182         return;
1183      }
1184
1185    inst->powered = powered;
1186
1187    if (!e_bluez_adapter_discoverable_get(inst->adapter, &discoverable))
1188      {
1189         _bluez_operation_error_show(_("Query adapter's discoverable."));
1190         return;
1191      }
1192
1193    inst->discoverable = discoverable;
1194 }
1195
1196 static void
1197 _default_adapter_callback(void          *data,
1198                           DBusMessage   *msg,
1199                           DBusError *err __UNUSED__)
1200 {
1201    E_Bluez_Module_Context *ctxt = data;
1202    const Eina_List *l;
1203    E_Bluez_Instance *inst;
1204    const char *path;
1205
1206    if (err && dbus_error_is_set(err))
1207      {
1208         dbus_error_free(err);
1209         return;
1210      }
1211
1212    if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
1213                              DBUS_TYPE_INVALID) == FALSE)
1214      return;
1215
1216    eina_stringshare_replace(&ctxt->default_adapter, path);
1217
1218    // TODO: instead of getting the default adapter, get the adapter for
1219    // each instance. See the mixer module.
1220    EINA_LIST_FOREACH(ctxt->instances, l, inst)
1221      {
1222         inst->adapter = e_bluez_adapter_get(path);
1223
1224         e_bluez_element_properties_sync_full
1225           (inst->adapter, _properties_sync_callback, inst);
1226      }
1227 }
1228
1229 static Eina_Bool
1230 _bluez_event_manager_in(void       *data,
1231                         int type    __UNUSED__,
1232                         void *event __UNUSED__)
1233 {
1234    E_Bluez_Module_Context *ctxt = data;
1235    E_Bluez_Element *element;
1236    Eina_List *l;
1237    E_Bluez_Instance *inst;
1238
1239    ctxt->has_manager = EINA_TRUE;
1240
1241    element = e_bluez_manager_get();
1242    if (!e_bluez_manager_default_adapter(_default_adapter_callback, ctxt))
1243      return ECORE_CALLBACK_DONE;
1244
1245    e_bluez_element_listener_add(element, _bluez_manager_changed, ctxt, NULL);
1246
1247    EINA_LIST_FOREACH(ctxt->instances, l, inst)
1248      _bluez_gadget_update(inst);
1249
1250    return ECORE_CALLBACK_PASS_ON;
1251 }
1252
1253 static Eina_Bool
1254 _bluez_event_manager_out(void       *data,
1255                          int type    __UNUSED__,
1256                          void *event __UNUSED__)
1257 {
1258    E_Bluez_Module_Context *ctxt = data;
1259    E_Bluez_Instance *inst;
1260    Eina_List *l;
1261
1262    ctxt->has_manager = EINA_FALSE;
1263    eina_stringshare_replace(&ctxt->default_adapter, NULL);
1264
1265    EINA_LIST_FOREACH(ctxt->instances, l, inst)
1266      _bluez_gadget_update(inst);
1267
1268    return ECORE_CALLBACK_PASS_ON;
1269 }
1270
1271 static Eina_Bool
1272 _bluez_event_element_updated(void       *data,
1273                              int type    __UNUSED__,
1274                              void *event __UNUSED__)
1275 {
1276    E_Bluez_Module_Context *ctxt = data;
1277    E_Bluez_Element *element = event;
1278    Eina_Bool powered, discoverable, discovering;
1279    E_Bluez_Instance *inst;
1280    Eina_List *l;
1281
1282    if (!e_bluez_element_is_adapter(element)) return ECORE_CALLBACK_PASS_ON;
1283
1284    if (!e_bluez_adapter_powered_get(element, &powered))
1285      powered = EINA_FALSE;
1286
1287    if (!e_bluez_adapter_discoverable_get(element, &discoverable))
1288      discoverable = EINA_FALSE;
1289
1290    if (!e_bluez_adapter_discovering_get(element, &discovering))
1291      discovering = EINA_FALSE;
1292
1293    EINA_LIST_FOREACH(ctxt->instances, l, inst)
1294      {
1295         if (inst->adapter != element) continue;
1296
1297         inst->powered = powered;
1298         inst->discoverable = discoverable;
1299         inst->discovering = discovering;
1300         _bluez_gadget_update(inst);
1301      }
1302
1303    return ECORE_CALLBACK_PASS_ON;
1304 }
1305
1306 static void
1307 _bluez_events_register(E_Bluez_Module_Context *ctxt)
1308 {
1309    ctxt->event.manager_in = ecore_event_handler_add
1310        (E_BLUEZ_EVENT_MANAGER_IN, _bluez_event_manager_in, ctxt);
1311    ctxt->event.manager_out = ecore_event_handler_add
1312        (E_BLUEZ_EVENT_MANAGER_OUT, _bluez_event_manager_out, ctxt);
1313    ctxt->event.element_updated = ecore_event_handler_add
1314        (E_BLUEZ_EVENT_ELEMENT_UPDATED, _bluez_event_element_updated, ctxt);
1315    ctxt->event.device_found = ecore_event_handler_add
1316        (E_BLUEZ_EVENT_DEVICE_FOUND, _bluez_event_devicefound, ctxt);
1317
1318    // TODO: E_BLUEZ_EVENT_DEVICE_DISAPPEARED
1319 }
1320
1321 static void
1322 _bluez_events_unregister(E_Bluez_Module_Context *ctxt)
1323 {
1324    if (ctxt->event.manager_in)
1325      ecore_event_handler_del(ctxt->event.manager_in);
1326    if (ctxt->event.manager_out)
1327      ecore_event_handler_del(ctxt->event.manager_out);
1328    if (ctxt->event.device_found)
1329      ecore_event_handler_del(ctxt->event.device_found);
1330 }
1331
1332 static void
1333 _bluez_agent_register(E_Bluez_Module_Context *ctxt)
1334 {
1335    E_DBus_Object *o;
1336
1337    ctxt->agent.iface = e_dbus_interface_new("org.bluez.Agent");
1338    if (!ctxt->agent.iface)
1339      return;
1340
1341    o = e_dbus_object_add(ctxt->agent.conn, _e_bluez_agent_path, ctxt);
1342    e_dbus_object_interface_attach(o, ctxt->agent.iface);
1343    e_dbus_interface_method_add
1344      (ctxt->agent.iface, "RequestPinCode", "o", "s", _bluez_request_pincode_cb);
1345    // TODO: RequestPasskey
1346    // TODO: RequestConfirmation
1347    // TODO: Authorize
1348    // TODO: DisplayPasskey
1349    // TODO: ConfirmModeChange
1350    // TODO: Cancel
1351    // TODO: Release
1352
1353    ctxt->agent.obj = o;
1354 }
1355
1356 static void
1357 _bluez_agent_unregister(E_Bluez_Module_Context *ctxt)
1358 {
1359    E_Object *o;
1360
1361    EINA_LIST_FREE(ctxt->agent.pending, o)
1362      e_object_del(o);
1363
1364    e_dbus_object_interface_detach(ctxt->agent.obj, ctxt->agent.iface);
1365    e_dbus_object_free(ctxt->agent.obj);
1366    e_dbus_interface_unref(ctxt->agent.iface);
1367 }
1368
1369 EAPI void *
1370 e_modapi_init(E_Module *m)
1371 {
1372    E_Bluez_Module_Context *ctxt = E_NEW(E_Bluez_Module_Context, 1);
1373    if (!ctxt)
1374      return NULL;
1375
1376    ctxt->agent.conn = e_dbus_bus_get(DBUS_BUS_SYSTEM);
1377    if ((!ctxt->agent.conn) || (!e_bluez_system_init(ctxt->agent.conn)))
1378      goto error_bluez_system_init;
1379
1380    bluez_mod = m;
1381
1382    if (_e_bluez_log_dom < 0)
1383      {
1384         _e_bluez_log_dom = eina_log_domain_register("ebluez", EINA_COLOR_ORANGE);
1385         if (_e_bluez_log_dom < 0)
1386           {
1387              //EINA_LOG_CRIT("could not register logging domain ebluez");
1388                goto error_log_domain;
1389           }
1390      }
1391
1392    _bluez_agent_register(ctxt);
1393    _bluez_actions_register(ctxt);
1394    e_gadcon_provider_register(&_gc_class);
1395
1396    _bluez_events_register(ctxt);
1397
1398    return ctxt;
1399
1400 error_log_domain:
1401    _e_bluez_log_dom = -1;
1402    bluez_mod = NULL;
1403    e_bluez_system_shutdown();
1404 error_bluez_system_init:
1405    E_FREE(ctxt);
1406    return NULL;
1407 }
1408
1409 static void
1410 _bluez_instances_free(E_Bluez_Module_Context *ctxt)
1411 {
1412    E_Bluez_Instance *inst;
1413    EINA_LIST_FREE(ctxt->instances, inst)
1414      {
1415         if (inst->popup)
1416           _bluez_popup_del(inst);
1417         if (inst->tip)
1418           _bluez_tip_del(inst);
1419
1420         e_object_del(E_OBJECT(inst->gcc));
1421      }
1422 }
1423
1424 EAPI int
1425 e_modapi_shutdown(E_Module *m)
1426 {
1427    E_Bluez_Module_Context *ctxt = m->data;
1428    E_Bluez_Element *element;
1429
1430    if (!ctxt)
1431      return 0;
1432
1433    element = e_bluez_manager_get();
1434    e_bluez_element_listener_del(element, _bluez_manager_changed, ctxt);
1435
1436    _bluez_events_unregister(ctxt);
1437    _bluez_instances_free(ctxt);
1438
1439    _bluez_actions_unregister(ctxt);
1440    _bluez_agent_unregister(ctxt);
1441    e_gadcon_provider_unregister(&_gc_class);
1442
1443    if (ctxt->poller.manager_changed)
1444      ecore_poller_del(ctxt->poller.manager_changed);
1445
1446    eina_stringshare_del(ctxt->default_adapter);
1447
1448    E_FREE(ctxt);
1449    bluez_mod = NULL;
1450
1451    e_bluez_system_shutdown();
1452
1453    return 1;
1454 }
1455
1456 EAPI int
1457 e_modapi_save(E_Module *m __UNUSED__)
1458 {
1459    return 1;
1460 }
1461