/* * TODO: * * HIGH: * * - check why return NULL from method call triggers cancel error * after timeout. * - find out why alias == address in _bluez_request_pincode_cb * - more complete agent support (handle requests from devices) * - handle device-disappeared events * - icon with device state (trusted, connected, paired) * * LOW: * * - configure (probably module) timeout to trigger automatic rescan. * - gadgets to show different adapters (see mixer module configuration) * - module to choose the default adapter (see mixer module configuration) * - icon with device class */ #include "e.h" #include "e_mod_main.h" static E_Module *bluez_mod = NULL; static char tmpbuf[PATH_MAX]; /* general purpose buffer, just use immediately */ static const char _e_bluez_agent_path[] = "/org/enlightenment/bluez/Agent"; const char _e_bluez_name[] = "bluez"; const char _e_bluez_Name[] = "Bluetooth Manager"; int _e_bluez_log_dom = -1; static void _bluez_gadget_update(E_Bluez_Instance *inst); static void _bluez_tip_update(E_Bluez_Instance *inst); static void _bluez_popup_update(E_Bluez_Instance *inst); struct bluez_pincode_data { void (*cb)(struct bluez_pincode_data *d); DBusMessage *msg; E_Bluez_Module_Context *ctxt; char *pincode; const char *alias; E_Dialog *dia; Evas_Object *entry; Eina_Bool canceled; }; const char * e_bluez_theme_path(void) { #define TF "/e-module-bluez.edj" size_t dirlen; dirlen = strlen(bluez_mod->dir); if (dirlen >= sizeof(tmpbuf) - sizeof(TF)) return NULL; memcpy(tmpbuf, bluez_mod->dir, dirlen); memcpy(tmpbuf + dirlen, TF, sizeof(TF)); return tmpbuf; #undef TF } static void _bluez_devices_clear(E_Bluez_Instance *inst) { E_Bluez_Instance_Device *d; EINA_LIST_FREE(inst->devices, d) { eina_stringshare_del(d->address); eina_stringshare_del(d->alias); free(d); } inst->address = NULL; inst->alias = NULL; } static void _bluez_discovery_cb(void *data, DBusMessage *msg __UNUSED__, DBusError *error) { E_Bluez_Instance *inst = data; char *label; if (error && dbus_error_is_set(error)) { _bluez_dbus_error_show(_("Cannot change adapter's discovery."), error); dbus_error_free(error); return; } inst->discovering = !inst->discovering; label = !inst->discovering ? _("Start Scan") : _("Stop Scan"); e_widget_button_label_set(inst->ui.button, label); } static void _bluez_create_paired_device_cb(void *data, DBusMessage *msg __UNUSED__, DBusError *error) { const char *alias = data; if (error && dbus_error_is_set(error)) { if (strcmp(error->name, "org.bluez.Error.AlreadyExists") != 0) _bluez_dbus_error_show(_("Cannot pair with device."), error); dbus_error_free(error); eina_stringshare_del(alias); return; } e_util_dialog_show (_("Bluetooth Manager"), _("Device '%s' successfully paired."), alias); eina_stringshare_del(alias); } static void _bluez_toggle_powered_cb(void *data, DBusMessage *msg __UNUSED__, DBusError *error) { E_Bluez_Instance *inst = data; if ((!error) || (!dbus_error_is_set(error))) { inst->powered_pending = EINA_FALSE; inst->powered = !inst->powered; if (!inst->powered) { _bluez_devices_clear(inst); if (inst->popup) _bluez_popup_update(inst); } _bluez_gadget_update(inst); return; } _bluez_dbus_error_show(_("Cannot toggle adapter's powered."), error); dbus_error_free(error); } void _bluez_toggle_powered(E_Bluez_Instance *inst) { Eina_Bool powered; if ((!inst) || (!inst->ctxt->has_manager)) { _bluez_operation_error_show(_("BlueZ Daemon is not running.")); return; } if (!inst->adapter) { _bluez_operation_error_show(_("No bluetooth adapter.")); return; } if (!e_bluez_adapter_powered_get(inst->adapter, &powered)) { _bluez_operation_error_show(_("Query adapter's powered.")); return; } powered = !powered; if (!e_bluez_adapter_powered_set (inst->adapter, powered, _bluez_toggle_powered_cb, inst)) { _bluez_operation_error_show(_("Query adapter's powered.")); return; } } static void _bluez_cb_toggle_powered(E_Object *obj __UNUSED__, const char *params __UNUSED__) { E_Bluez_Module_Context *ctxt; const Eina_List *l; E_Bluez_Instance *inst; if (!bluez_mod) return; ctxt = bluez_mod->data; EINA_LIST_FOREACH(ctxt->instances, l, inst) if (inst->adapter) _bluez_toggle_powered(inst); } static void _bluez_popup_del(E_Bluez_Instance *inst); static Eina_Bool _bluez_popup_input_window_mouse_up_cb(void *data, int type __UNUSED__, void *event) { Ecore_Event_Mouse_Button *ev = event; E_Bluez_Instance *inst = data; if (ev->window != inst->ui.input.win) return ECORE_CALLBACK_PASS_ON; _bluez_popup_del(inst); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _bluez_popup_input_window_key_down_cb(void *data, int type __UNUSED__, void *event) { Ecore_Event_Key *ev = event; E_Bluez_Instance *inst = data; const char *keysym; if (ev->window != inst->ui.input.win) return ECORE_CALLBACK_PASS_ON; keysym = ev->key; if (strcmp(keysym, "Escape") == 0) _bluez_popup_del(inst); return ECORE_CALLBACK_PASS_ON; } static void _bluez_popup_input_window_destroy(E_Bluez_Instance *inst) { ecore_x_window_free(inst->ui.input.win); inst->ui.input.win = 0; ecore_event_handler_del(inst->ui.input.mouse_up); inst->ui.input.mouse_up = NULL; ecore_event_handler_del(inst->ui.input.key_down); inst->ui.input.key_down = NULL; } static void _bluez_popup_input_window_create(E_Bluez_Instance *inst) { Ecore_X_Window_Configure_Mask mask; Ecore_X_Window w, popup_w; E_Manager *man; man = e_manager_current_get(); w = ecore_x_window_input_new(man->root, 0, 0, man->w, man->h); mask = (ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE | ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING); popup_w = inst->popup->win->evas_win; ecore_x_window_configure(w, mask, 0, 0, 0, 0, 0, popup_w, ECORE_X_WINDOW_STACK_BELOW); ecore_x_window_show(w); inst->ui.input.mouse_up = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, _bluez_popup_input_window_mouse_up_cb, inst); inst->ui.input.key_down = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, _bluez_popup_input_window_key_down_cb, inst); inst->ui.input.win = w; } static void _bluez_popup_cb_powered_changed(void *data, Evas_Object *obj) { E_Bluez_Instance *inst = data; E_Bluez_Module_Context *ctxt = inst->ctxt; Eina_Bool powered = e_widget_check_checked_get(obj); if ((!ctxt) || (!ctxt->has_manager)) { _bluez_operation_error_show(_("BlueZ Daemon is not running.")); return; } if (!inst->adapter) { _bluez_operation_error_show(_("No bluetooth adapter.")); return; } if (!e_bluez_adapter_powered_set (inst->adapter, powered, _bluez_toggle_powered_cb, inst)) { _bluez_operation_error_show (_("Cannot toggle adapter's powered.")); return; } inst->powered_pending = EINA_TRUE; } static void _bluez_pincode_ask_cb(struct bluez_pincode_data *d) { DBusMessage *reply; if (!d->pincode) { e_util_dialog_show(_("Bluetooth Manager"), _("Invalid Pin Code.")); return; } reply = dbus_message_new_method_return(d->msg); dbus_message_append_args (reply, DBUS_TYPE_STRING, &d->pincode, DBUS_TYPE_INVALID); dbus_message_set_no_reply(reply, EINA_TRUE); e_dbus_message_send(d->ctxt->agent.conn, reply, NULL, -1, NULL); } static void bluez_pincode_ask_ok(void *data, E_Dialog *dia) { struct bluez_pincode_data *d = data; d->canceled = EINA_FALSE; e_object_del(E_OBJECT(dia)); } static void bluez_pincode_ask_cancel(void *data, E_Dialog *dia) { struct bluez_pincode_data *d = data; d->canceled = EINA_TRUE; e_object_del(E_OBJECT(dia)); } static void bluez_pincode_ask_del(void *data) { E_Dialog *dia = data; struct bluez_pincode_data *d = e_object_data_get(E_OBJECT(dia)); if (!d->canceled) d->cb(d); d->ctxt->agent.pending = eina_list_remove(d->ctxt->agent.pending, dia); free(d->pincode); dbus_message_unref(d->msg); eina_stringshare_del(d->alias); E_FREE(d); } static void bluez_pincode_ask_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event) { Evas_Event_Key_Down *ev = event; struct bluez_pincode_data *d = data; if (strcmp(ev->keyname, "Return") == 0) bluez_pincode_ask_ok(d, d->dia); else if (strcmp(ev->keyname, "Escape") == 0) bluez_pincode_ask_cancel(d, d->dia); } static void bluez_pincode_ask(void (*cb)(struct bluez_pincode_data *), DBusMessage *msg, const char *alias, E_Bluez_Module_Context *ctxt) { struct bluez_pincode_data *d; Evas_Object *list, *o; Evas *evas; char buf[512]; int mw, mh; if (!cb) return; d = E_NEW(struct bluez_pincode_data, 1); if (!d) return; d->cb = cb; d->ctxt = ctxt; d->alias = eina_stringshare_add(alias); d->msg = dbus_message_ref(msg); d->canceled = EINA_TRUE; /* closing the dialog defaults to cancel */ d->dia = e_dialog_new(NULL, "E", "bluez_ask_pincode"); snprintf(buf, sizeof(buf), _("Pairing with device '%s'"), alias); e_dialog_title_set(d->dia, buf); e_dialog_icon_set(d->dia, "dialog-ask", 32); e_dialog_border_icon_set(d->dia, "dialog-ask"); evas = d->dia->win->evas; list = e_widget_list_add(evas, 0, 0); o = edje_object_add(evas); e_theme_edje_object_set(o, "base/theme/dialog", "e/widgets/dialog/text"); snprintf(buf, sizeof(buf), _("Enter the PIN code: ")); edje_object_part_text_set(o, "e.textblock.message", buf); edje_object_size_min_calc(o, &mw, &mh); evas_object_size_hint_min_set(o, mw, mh); evas_object_resize(o, mw, mh); evas_object_show(o); e_widget_list_object_append(list, o, 1, 1, 0.5); d->entry = o = e_widget_entry_add(evas, &d->pincode, NULL, NULL, NULL); e_widget_entry_password_set(o, 0); evas_object_show(o); e_widget_list_object_append(list, o, 1, 0, 0.0); e_widget_size_min_get(list, &mw, &mh); if (mw < 200) mw = 200; if (mh < 60) mh = 60; e_dialog_content_set(d->dia, list, mw, mh); e_dialog_button_add (d->dia, _("Ok"), NULL, bluez_pincode_ask_ok, d); e_dialog_button_add (d->dia, _("Cancel"), NULL, bluez_pincode_ask_cancel, d); evas_object_event_callback_add (d->dia->bg_object, EVAS_CALLBACK_KEY_DOWN, bluez_pincode_ask_key_down, d); e_object_del_attach_func_set (E_OBJECT(d->dia), bluez_pincode_ask_del); e_object_data_set(E_OBJECT(d->dia), d); e_dialog_button_focus_num(d->dia, 0); e_widget_focus_set(d->entry, 1); e_win_centered_set(d->dia->win, 1); e_dialog_show(d->dia); ctxt->agent.pending = eina_list_append(ctxt->agent.pending, d->dia); } static DBusMessage * _bluez_request_pincode_cb(E_DBus_Object *obj, DBusMessage *msg) { E_Bluez_Module_Context *ctxt = e_dbus_object_data_get(obj); E_Bluez_Element *element; const char *path; const char *alias; // TODO: seems that returning NULL is causing pin code rquest to be canceled! if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return NULL; element = e_bluez_device_get(path); if (!element) alias = path; else { if (!e_bluez_device_alias_get(element, &alias)) { if (!e_bluez_device_name_get(element, &alias)) alias = path; } } // TODO: find out why alias == address, then remove debug: fprintf(stderr, ">>> request pin code of '%s' (%s)\n", alias, path); bluez_pincode_ask(_bluez_pincode_ask_cb, msg, alias, ctxt); return NULL; } static void _bluez_popup_cb_scan(void *data, void *data2 __UNUSED__) { E_Bluez_Instance *inst = data; int ret; if (!inst->adapter) ret = 0; else if (inst->discovering) ret = e_bluez_adapter_stop_discovery (inst->adapter, _bluez_discovery_cb, inst); else { inst->last_scan = ecore_loop_time_get(); _bluez_devices_clear(inst); ret = e_bluez_adapter_start_discovery (inst->adapter, _bluez_discovery_cb, inst); _bluez_popup_update(inst); } if (!ret) ERR("Failed on discovery procedure"); } static void _bluez_popup_cb_controls(void *data, void *data2 __UNUSED__) { E_Bluez_Instance *inst = data; if (inst->popup) _bluez_popup_del(inst); if (inst->conf_dialog) return; if (!inst->adapter) return; inst->conf_dialog = e_bluez_config_dialog_new(NULL, inst); } static void _bluez_popup_device_selected(void *data) { E_Bluez_Instance *inst = data; const char *address = inst->address; const char *alias; const char *cap = "DisplayYesNo"; const E_Bluez_Instance_Device *d; const Eina_List *l; if (inst->popup) _bluez_popup_del(inst); if (!address) { ERR("no device selected for pairing."); return; } inst->alias = address; EINA_LIST_FOREACH(inst->devices, l, d) { if (address == d->alias) { inst->alias = d->alias; break; } } if (!inst->alias) { ERR("device %s does not have an alias.", address); return; } alias = eina_stringshare_ref(inst->alias); if (!e_bluez_adapter_create_paired_device (inst->adapter, _e_bluez_agent_path, cap, address, _bluez_create_paired_device_cb, alias)) { eina_stringshare_del(alias); return; } } static Eina_Bool _bluez_event_devicefound(void *data, int type __UNUSED__, void *event) { E_Bluez_Module_Context *ctxt = data; E_Bluez_Device_Found *device = event; E_Bluez_Instance *inst; const Eina_List *l_inst; const char *alias; // TODO: get properties such as paired, connected, trusted, class, icon... // TODO: check if the adapter contains device->name and if so get path. alias = e_bluez_devicefound_alias_get(device); EINA_LIST_FOREACH(ctxt->instances, l_inst, inst) { const Eina_List *l_dev; E_Bluez_Instance_Device *dev; Eina_Bool found = EINA_FALSE; if (inst->adapter != device->adapter) continue; EINA_LIST_FOREACH(inst->devices, l_dev, dev) { if (dev->address == device->name) { found = EINA_TRUE; break; } } if (found) continue; dev = malloc(sizeof(E_Bluez_Instance_Device)); if (!dev) continue; dev->address = eina_stringshare_ref(device->name); dev->alias = eina_stringshare_ref(alias); inst->devices = eina_list_append(inst->devices, dev); if (inst->ui.list) { e_widget_ilist_append (inst->ui.list, NULL, dev->alias, _bluez_popup_device_selected, inst, dev->address); e_widget_ilist_go(inst->ui.list); } } return 1; } static void _bluez_popup_update(E_Bluez_Instance *inst) { Evas_Object *list = inst->ui.list; int selected; const char *label; E_Bluez_Instance_Device *d; Eina_List *l; /* TODO: replace this with a scroller + list of edje * objects that are more full of features */ selected = e_widget_ilist_selected_get(list); e_widget_ilist_freeze(list); e_widget_ilist_clear(list); EINA_LIST_FOREACH(inst->devices, l, d) { e_widget_ilist_append (inst->ui.list, NULL, d->alias, _bluez_popup_device_selected, inst, d->address); } if (selected >= 0) { inst->first_selection = EINA_TRUE; e_widget_ilist_selected_set(list, selected); } else inst->first_selection = EINA_FALSE; e_widget_ilist_go(list); e_widget_check_checked_set(inst->ui.powered, inst->powered); label = inst->discovering ? _("Stop Scan") : _("Start Scan"); e_widget_button_label_set(inst->ui.button, label); e_widget_disabled_set(inst->ui.button, !inst->powered); } static void _bluez_popup_del(E_Bluez_Instance *inst) { _bluez_popup_input_window_destroy(inst); e_object_del(E_OBJECT(inst->popup)); inst->popup = NULL; } static void _bluez_popup_new(E_Bluez_Instance *inst) { Evas_Object *ol; Evas *evas; Evas_Coord mw, mh; const char *label; Eina_Bool b, needs_scan = EINA_FALSE; if (inst->popup) { e_gadcon_popup_show(inst->popup); return; } if (!inst->adapter) { _bluez_operation_error_show(_("No bluetooth adapter.")); return; } if (!e_bluez_adapter_discovering_get(inst->adapter, &b)) { _bluez_operation_error_show(_("Can't get Discovering property")); return; } inst->discovering = b; // maybe auto-scan if did not in the last 30 minutes? // seems scan will hurt things like bluetooth audio playback, so don't do it if ((!inst->discovering) && (inst->last_scan <= 0.0) && (inst->ui.powered)) { label = _("Stop Scan"); needs_scan = EINA_TRUE; } else label = inst->discovering ? _("Stop Scan") : _("Start Scan"); inst->popup = e_gadcon_popup_new(inst->gcc); evas = inst->popup->win->evas; ol = e_widget_list_add(evas, 0, 0); // TODO: get this size from edj inst->ui.list = e_widget_ilist_add(evas, 32, 32, &inst->address); e_widget_size_min_set(inst->ui.list, 180, 100); e_widget_list_object_append(ol, inst->ui.list, 1, 1, 0.5); inst->powered = inst->powered; inst->ui.powered = e_widget_check_add(evas, _("Powered"), &inst->powered); e_widget_on_change_hook_set (inst->ui.powered, _bluez_popup_cb_powered_changed, inst); e_widget_list_object_append(ol, inst->ui.powered, 1, 0, 0.5); inst->ui.button = e_widget_button_add (evas, label, NULL, _bluez_popup_cb_scan, inst, NULL); e_widget_list_object_append(ol, inst->ui.button, 1, 0, 0.5); inst->ui.control = e_widget_button_add (evas, _("Controls"), NULL, _bluez_popup_cb_controls, inst, NULL); e_widget_list_object_append(ol, inst->ui.control, 1, 0, 0.5); _bluez_popup_update(inst); e_widget_size_min_get(ol, &mw, &mh); if (mh < 200) mh = 200; if (mw < 200) mw = 200; e_widget_size_min_set(ol, mw, mh); e_gadcon_popup_content_set(inst->popup, ol); e_gadcon_popup_show(inst->popup); _bluez_popup_input_window_create(inst); if (needs_scan) _bluez_popup_cb_scan(inst, NULL); } static void _bluez_menu_cb_post(void *data, E_Menu *menu __UNUSED__) { E_Bluez_Instance *inst = data; if ((!inst) || (!inst->menu)) return; if (inst->menu) { e_object_del(E_OBJECT(inst->menu)); inst->menu = NULL; } } static void _bluez_menu_cb_cfg(void *data, E_Menu *menu __UNUSED__, E_Menu_Item *mi __UNUSED__) { E_Bluez_Instance *inst = data; if (inst->popup) _bluez_popup_del(inst); if (inst->conf_dialog) return; if (!inst->adapter) return; inst->conf_dialog = e_bluez_config_dialog_new(NULL, inst); } static void _bluez_menu_new(E_Bluez_Instance *inst, Evas_Event_Mouse_Down *ev) { E_Zone *zone; E_Menu *m; E_Menu_Item *mi; int x, y; zone = e_util_zone_current_get(e_manager_current_get()); m = e_menu_new(); mi = e_menu_item_new(m); e_menu_item_label_set(mi, _("Settings")); e_util_menu_item_theme_icon_set(mi, "configure"); e_menu_item_callback_set(mi, _bluez_menu_cb_cfg, inst); m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0); e_menu_post_deactivate_callback_set(m, _bluez_menu_cb_post, inst); inst->menu = m; e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL); e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y, 1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp); evas_event_feed_mouse_up(inst->gcc->gadcon->evas, ev->button, EVAS_BUTTON_NONE, ev->timestamp, NULL); } static void _bluez_tip_new(E_Bluez_Instance *inst) { Evas *e; inst->tip = e_gadcon_popup_new(inst->gcc); if (!inst->tip) return; e = inst->tip->win->evas; inst->o_tip = edje_object_add(e); e_theme_edje_object_set(inst->o_tip, "base/theme/modules/bluez/tip", "e/modules/bluez/tip"); _bluez_tip_update(inst); e_gadcon_popup_content_set(inst->tip, inst->o_tip); e_gadcon_popup_show(inst->tip); } static void _bluez_tip_del(E_Bluez_Instance *inst) { evas_object_del(inst->o_tip); e_object_del(E_OBJECT(inst->tip)); inst->tip = NULL; inst->o_tip = NULL; } static void _bluez_cb_mouse_down(void *data, Evas *evas __UNUSED__, Evas_Object *obj __UNUSED__, void *event) { E_Bluez_Instance *inst; Evas_Event_Mouse_Down *ev; inst = data; if (!inst) return; ev = event; if (ev->button == 1) { if (!inst->popup) _bluez_popup_new(inst); else _bluez_popup_del(inst); } else if (ev->button == 2) _bluez_toggle_powered(inst); else if ((ev->button == 3) && (!inst->menu)) _bluez_menu_new(inst, ev); } static void _bluez_cb_mouse_in(void *data, Evas *evas __UNUSED__, Evas_Object *obj __UNUSED__, void *event __UNUSED__) { E_Bluez_Instance *inst = data; if (inst->tip) return; _bluez_tip_new(inst); } static void _bluez_cb_mouse_out(void *data, Evas *evas __UNUSED__, Evas_Object *obj __UNUSED__, void *event __UNUSED__) { E_Bluez_Instance *inst = data; if (!inst->tip) return; _bluez_tip_del(inst); } static void _bluez_edje_view_update(E_Bluez_Instance *inst, Evas_Object *o) { E_Bluez_Module_Context *ctxt = inst->ctxt; const char *name; if (!ctxt->has_manager) { edje_object_part_text_set(o, "e.text.powered", ""); edje_object_part_text_set(o, "e.text.status", ""); edje_object_signal_emit(o, "e,changed,service,none", "e"); edje_object_part_text_set(o, "e.text.name", _("No Bluetooth daemon")); edje_object_signal_emit(o, "e,changed,name", "e"); return; } if (!inst->adapter) { edje_object_part_text_set(o, "e.text.powered", ""); edje_object_part_text_set(o, "e.text.status", ""); edje_object_signal_emit(o, "e,changed,off", "e"); edje_object_part_text_set(o, "e.text.name", _("No Bluetooth adapter")); edje_object_signal_emit(o, "e,changed,name", "e"); return; } if (!e_bluez_adapter_name_get(inst->adapter, &name)) name = ""; edje_object_part_text_set(o, "e.text.name", name); edje_object_signal_emit(o, "e,changed,name", "e"); if (inst->powered) { if (inst->discoverable) { edje_object_signal_emit(o, "e,changed,powered", "e"); edje_object_part_text_set (o, "e.text.status", _("Bluetooth is powered and discoverable.")); } else { edje_object_signal_emit(o, "e,changed,hidden", "e"); edje_object_part_text_set (o, "e.text.status", _("Bluetooth is powered and hidden.")); } } else { edje_object_signal_emit(o, "e,changed,off", "e"); edje_object_part_text_set(o, "e.text.status", _("Bluetooth is off.")); } } static void _bluez_tip_update(E_Bluez_Instance *inst) { _bluez_edje_view_update(inst, inst->o_tip); } static void _bluez_gadget_update(E_Bluez_Instance *inst) { E_Bluez_Module_Context *ctxt = inst->ctxt; if (inst->popup && ((!ctxt->has_manager) || (!inst->adapter))) _bluez_popup_del(inst); if (inst->popup) _bluez_popup_update(inst); if (inst->tip) _bluez_tip_update(inst); _bluez_edje_view_update(inst, inst->ui.gadget); } /* Gadcon Api Functions */ static E_Gadcon_Client * _gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) { E_Bluez_Instance *inst; E_Bluez_Module_Context *ctxt; if (!bluez_mod) return NULL; ctxt = bluez_mod->data; inst = E_NEW(E_Bluez_Instance, 1); inst->ctxt = ctxt; inst->ui.gadget = edje_object_add(gc->evas); e_theme_edje_object_set(inst->ui.gadget, "base/theme/modules/bluez", "e/modules/bluez/main"); inst->gcc = e_gadcon_client_new(gc, name, id, style, inst->ui.gadget); inst->gcc->data = inst; evas_object_event_callback_add (inst->ui.gadget, EVAS_CALLBACK_MOUSE_DOWN, _bluez_cb_mouse_down, inst); evas_object_event_callback_add (inst->ui.gadget, EVAS_CALLBACK_MOUSE_IN, _bluez_cb_mouse_in, inst); evas_object_event_callback_add (inst->ui.gadget, EVAS_CALLBACK_MOUSE_OUT, _bluez_cb_mouse_out, inst); // TODO: instead of getting the default adapter, get the adapter for // each instance. See the mixer module. if (ctxt->default_adapter) inst->adapter = e_bluez_adapter_get(ctxt->default_adapter); else inst->adapter = NULL; if (inst->adapter) { Eina_Bool powered, discoverable, discovering; if (e_bluez_adapter_powered_get(inst->adapter, &powered)) inst->powered = powered; if (e_bluez_adapter_discoverable_get(inst->adapter, &discoverable)) inst->discoverable = discoverable; if (e_bluez_adapter_discovering_get(inst->adapter, &discovering)) inst->discovering = discovering; } _bluez_gadget_update(inst); ctxt->instances = eina_list_append(ctxt->instances, inst); return inst->gcc; } static void _gc_shutdown(E_Gadcon_Client *gcc) { E_Bluez_Module_Context *ctxt; E_Bluez_Instance *inst; if (!bluez_mod) return; ctxt = bluez_mod->data; if (!ctxt) return; inst = gcc->data; if (!inst) return; if (inst->menu) { e_menu_post_deactivate_callback_set(inst->menu, NULL, NULL); e_object_del(E_OBJECT(inst->menu)); } evas_object_del(inst->ui.gadget); _bluez_devices_clear(inst); ctxt->instances = eina_list_remove(ctxt->instances, inst); E_FREE(inst); } static void _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient __UNUSED__) { e_gadcon_client_aspect_set(gcc, 16, 16); e_gadcon_client_min_size_set(gcc, 16, 16); } static const char * _gc_label(E_Gadcon_Client_Class *client_class __UNUSED__) { return _(_e_bluez_Name); } static Evas_Object * _gc_icon(E_Gadcon_Client_Class *client_class __UNUSED__, Evas *evas) { Evas_Object *o; o = edje_object_add(evas); edje_object_file_set(o, e_bluez_theme_path(), "icon"); return o; } static const char * _gc_id_new(E_Gadcon_Client_Class *client_class __UNUSED__) { E_Bluez_Module_Context *ctxt; if (!bluez_mod) return NULL; ctxt = bluez_mod->data; if (!ctxt) return NULL; snprintf(tmpbuf, sizeof(tmpbuf), "bluez.%d", eina_list_count(ctxt->instances)); return tmpbuf; } static const E_Gadcon_Client_Class _gc_class = { GADCON_CLIENT_CLASS_VERSION, _e_bluez_name, { _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, e_gadcon_site_is_not_toolbar }, E_GADCON_CLIENT_STYLE_PLAIN }; EAPI E_Module_Api e_modapi = {E_MODULE_API_VERSION, _e_bluez_Name}; static const char _act_toggle_powered[] = "toggle_powered"; static const char _lbl_toggle_powered[] = "Toggle Powered"; static void _bluez_actions_register(E_Bluez_Module_Context *ctxt) { ctxt->actions.toggle_powered = e_action_add(_act_toggle_powered); if (ctxt->actions.toggle_powered) { ctxt->actions.toggle_powered->func.go = _bluez_cb_toggle_powered; e_action_predef_name_set (_(_e_bluez_Name), _(_lbl_toggle_powered), _act_toggle_powered, NULL, NULL, 0); } } static void _bluez_actions_unregister(E_Bluez_Module_Context *ctxt) { if (ctxt->actions.toggle_powered) { e_action_predef_name_del(_(_e_bluez_Name), _(_lbl_toggle_powered)); e_action_del(_act_toggle_powered); } } static Eina_Bool _bluez_manager_changed_do(void *data) { E_Bluez_Module_Context *ctxt = data; //FIXME: reload the default adapter maybe? ctxt->poller.manager_changed = NULL; return ECORE_CALLBACK_CANCEL; } static void _bluez_manager_changed(void *data, const E_Bluez_Element *element __UNUSED__) { E_Bluez_Module_Context *ctxt = data; if (ctxt->poller.manager_changed) ecore_poller_del(ctxt->poller.manager_changed); ctxt->poller.manager_changed = ecore_poller_add (ECORE_POLLER_CORE, 1, _bluez_manager_changed_do, ctxt); } static void _properties_sync_callback(void *data, DBusMessage *msg __UNUSED__, DBusError *err) { E_Bluez_Instance *inst = data; Eina_Bool powered; Eina_Bool discoverable; if (err && dbus_error_is_set(err)) { dbus_error_free(err); return; } if (!e_bluez_adapter_powered_get(inst->adapter, &powered)) { _bluez_operation_error_show(_("Query adapter's powered.")); return; } inst->powered = powered; if (!e_bluez_adapter_discoverable_get(inst->adapter, &discoverable)) { _bluez_operation_error_show(_("Query adapter's discoverable.")); return; } inst->discoverable = discoverable; } static void _default_adapter_callback(void *data, DBusMessage *msg, DBusError *err __UNUSED__) { E_Bluez_Module_Context *ctxt = data; const Eina_List *l; E_Bluez_Instance *inst; const char *path; if (err && dbus_error_is_set(err)) { dbus_error_free(err); return; } if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return; eina_stringshare_replace(&ctxt->default_adapter, path); // TODO: instead of getting the default adapter, get the adapter for // each instance. See the mixer module. EINA_LIST_FOREACH(ctxt->instances, l, inst) { inst->adapter = e_bluez_adapter_get(path); e_bluez_element_properties_sync_full (inst->adapter, _properties_sync_callback, inst); } } static Eina_Bool _bluez_event_manager_in(void *data, int type __UNUSED__, void *event __UNUSED__) { E_Bluez_Module_Context *ctxt = data; E_Bluez_Element *element; Eina_List *l; E_Bluez_Instance *inst; ctxt->has_manager = EINA_TRUE; element = e_bluez_manager_get(); if (!e_bluez_manager_default_adapter(_default_adapter_callback, ctxt)) return ECORE_CALLBACK_DONE; e_bluez_element_listener_add(element, _bluez_manager_changed, ctxt, NULL); EINA_LIST_FOREACH(ctxt->instances, l, inst) _bluez_gadget_update(inst); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _bluez_event_manager_out(void *data, int type __UNUSED__, void *event __UNUSED__) { E_Bluez_Module_Context *ctxt = data; E_Bluez_Instance *inst; Eina_List *l; ctxt->has_manager = EINA_FALSE; eina_stringshare_replace(&ctxt->default_adapter, NULL); EINA_LIST_FOREACH(ctxt->instances, l, inst) _bluez_gadget_update(inst); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _bluez_event_element_updated(void *data, int type __UNUSED__, void *event __UNUSED__) { E_Bluez_Module_Context *ctxt = data; E_Bluez_Element *element = event; Eina_Bool powered, discoverable, discovering; E_Bluez_Instance *inst; Eina_List *l; if (!e_bluez_element_is_adapter(element)) return ECORE_CALLBACK_PASS_ON; if (!e_bluez_adapter_powered_get(element, &powered)) powered = EINA_FALSE; if (!e_bluez_adapter_discoverable_get(element, &discoverable)) discoverable = EINA_FALSE; if (!e_bluez_adapter_discovering_get(element, &discovering)) discovering = EINA_FALSE; EINA_LIST_FOREACH(ctxt->instances, l, inst) { if (inst->adapter != element) continue; inst->powered = powered; inst->discoverable = discoverable; inst->discovering = discovering; _bluez_gadget_update(inst); } return ECORE_CALLBACK_PASS_ON; } static void _bluez_events_register(E_Bluez_Module_Context *ctxt) { ctxt->event.manager_in = ecore_event_handler_add (E_BLUEZ_EVENT_MANAGER_IN, _bluez_event_manager_in, ctxt); ctxt->event.manager_out = ecore_event_handler_add (E_BLUEZ_EVENT_MANAGER_OUT, _bluez_event_manager_out, ctxt); ctxt->event.element_updated = ecore_event_handler_add (E_BLUEZ_EVENT_ELEMENT_UPDATED, _bluez_event_element_updated, ctxt); ctxt->event.device_found = ecore_event_handler_add (E_BLUEZ_EVENT_DEVICE_FOUND, _bluez_event_devicefound, ctxt); // TODO: E_BLUEZ_EVENT_DEVICE_DISAPPEARED } static void _bluez_events_unregister(E_Bluez_Module_Context *ctxt) { if (ctxt->event.manager_in) ecore_event_handler_del(ctxt->event.manager_in); if (ctxt->event.manager_out) ecore_event_handler_del(ctxt->event.manager_out); if (ctxt->event.device_found) ecore_event_handler_del(ctxt->event.device_found); } static void _bluez_agent_register(E_Bluez_Module_Context *ctxt) { E_DBus_Object *o; ctxt->agent.iface = e_dbus_interface_new("org.bluez.Agent"); if (!ctxt->agent.iface) return; o = e_dbus_object_add(ctxt->agent.conn, _e_bluez_agent_path, ctxt); e_dbus_object_interface_attach(o, ctxt->agent.iface); e_dbus_interface_method_add (ctxt->agent.iface, "RequestPinCode", "o", "s", _bluez_request_pincode_cb); // TODO: RequestPasskey // TODO: RequestConfirmation // TODO: Authorize // TODO: DisplayPasskey // TODO: ConfirmModeChange // TODO: Cancel // TODO: Release ctxt->agent.obj = o; } static void _bluez_agent_unregister(E_Bluez_Module_Context *ctxt) { E_Object *o; EINA_LIST_FREE(ctxt->agent.pending, o) e_object_del(o); e_dbus_object_interface_detach(ctxt->agent.obj, ctxt->agent.iface); e_dbus_object_free(ctxt->agent.obj); e_dbus_interface_unref(ctxt->agent.iface); } EAPI void * e_modapi_init(E_Module *m) { E_Bluez_Module_Context *ctxt = E_NEW(E_Bluez_Module_Context, 1); if (!ctxt) return NULL; ctxt->agent.conn = e_dbus_bus_get(DBUS_BUS_SYSTEM); if ((!ctxt->agent.conn) || (!e_bluez_system_init(ctxt->agent.conn))) goto error_bluez_system_init; bluez_mod = m; if (_e_bluez_log_dom < 0) { _e_bluez_log_dom = eina_log_domain_register("ebluez", EINA_COLOR_ORANGE); if (_e_bluez_log_dom < 0) { //EINA_LOG_CRIT("could not register logging domain ebluez"); goto error_log_domain; } } _bluez_agent_register(ctxt); _bluez_actions_register(ctxt); e_gadcon_provider_register(&_gc_class); _bluez_events_register(ctxt); return ctxt; error_log_domain: _e_bluez_log_dom = -1; bluez_mod = NULL; e_bluez_system_shutdown(); error_bluez_system_init: E_FREE(ctxt); return NULL; } static void _bluez_instances_free(E_Bluez_Module_Context *ctxt) { E_Bluez_Instance *inst; EINA_LIST_FREE(ctxt->instances, inst) { if (inst->popup) _bluez_popup_del(inst); if (inst->tip) _bluez_tip_del(inst); e_object_del(E_OBJECT(inst->gcc)); } } EAPI int e_modapi_shutdown(E_Module *m) { E_Bluez_Module_Context *ctxt = m->data; E_Bluez_Element *element; if (!ctxt) return 0; element = e_bluez_manager_get(); e_bluez_element_listener_del(element, _bluez_manager_changed, ctxt); _bluez_events_unregister(ctxt); _bluez_instances_free(ctxt); _bluez_actions_unregister(ctxt); _bluez_agent_unregister(ctxt); e_gadcon_provider_unregister(&_gc_class); if (ctxt->poller.manager_changed) ecore_poller_del(ctxt->poller.manager_changed); eina_stringshare_del(ctxt->default_adapter); E_FREE(ctxt); bluez_mod = NULL; e_bluez_system_shutdown(); return 1; } EAPI int e_modapi_save(E_Module *m __UNUSED__) { return 1; }