From 2e5be79c451fae48f97456369c12b3c0af08b5d8 Mon Sep 17 00:00:00 2001 From: "Carsten Haitzler (Rasterman)" Date: Thu, 10 May 2018 13:52:06 +0900 Subject: [PATCH] bluez5 support - a new module with gadget etc. Summary: bluez4 support is now basically dead. nothing ships it anymore. bluez5 is a new api that is rather different so new code. also a new gui with more complete features etc. not everything is done as i'd like. need: 1. many more icons for device types (60-70 maybe?) 2. a few specific custom icons for some action buttons (like pair/unpair) 3. icons for group headers 4. gadget status - the gagdte itself displays zero status. it's a button to display a popup. that's all. Reviewers: zmike! Subscribers: devilhorns, cedric Tags: #enlightenment-git Differential Revision: https://phab.enlightenment.org/D6148 --- meson_options.txt | 4 + src/bin/e_config.c | 38 ++ src/bin/e_config.h | 2 +- src/bin/e_module.c | 1 + src/modules/bluez5/bz.c | 109 ++++ src/modules/bluez5/bz.h | 234 ++++++++ src/modules/bluez5/bz_agent.c | 346 +++++++++++ src/modules/bluez5/bz_obj.c | 760 +++++++++++++++++++++++++ src/modules/bluez5/e-module-bluez5.edj | Bin 0 -> 59845 bytes src/modules/bluez5/e_mod_agent.c | 143 +++++ src/modules/bluez5/e_mod_main.c | 305 ++++++++++ src/modules/bluez5/e_mod_main.h | 68 +++ src/modules/bluez5/e_mod_popup.c | 714 +++++++++++++++++++++++ src/modules/bluez5/e_mod_util.c | 262 +++++++++ src/modules/bluez5/meson.build | 9 + src/modules/bluez5/module.desktop | 13 + src/modules/meson.build | 1 + 17 files changed, 3008 insertions(+), 1 deletion(-) create mode 100644 src/modules/bluez5/bz.c create mode 100644 src/modules/bluez5/bz.h create mode 100644 src/modules/bluez5/bz_agent.c create mode 100644 src/modules/bluez5/bz_obj.c create mode 100644 src/modules/bluez5/e-module-bluez5.edj create mode 100644 src/modules/bluez5/e_mod_agent.c create mode 100644 src/modules/bluez5/e_mod_main.c create mode 100644 src/modules/bluez5/e_mod_main.h create mode 100644 src/modules/bluez5/e_mod_popup.c create mode 100644 src/modules/bluez5/e_mod_util.c create mode 100644 src/modules/bluez5/meson.build create mode 100644 src/modules/bluez5/module.desktop diff --git a/meson_options.txt b/meson_options.txt index 234802766..c570b0522 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -111,6 +111,10 @@ option('bluez4', type: 'boolean', value: true, description: 'enable bluez4 module: (default=true)') +option('bluez5', + type: 'boolean', + value: true, + description: 'enable bluez5 module: (default=true)') option('clock', type: 'boolean', value: true, diff --git a/src/bin/e_config.c b/src/bin/e_config.c index 1feb31bbc..5abf91bae 100644 --- a/src/bin/e_config.c +++ b/src/bin/e_config.c @@ -1525,6 +1525,44 @@ e_config_load(void) CONFIG_VERSION_UPDATE_INFO(25); e_config_save_queue(); } + CONFIG_VERSION_CHECK(26) + { + // move from bluez4 to bluez5 automatically. you can manually + // move back if that works better for you... + Eina_List *l, *ll; + E_Config_Module *em; + E_Config_Gadcon *gc; + E_Config_Gadcon_Client *gcc; + + EINA_LIST_FOREACH(e_config->modules, l, em) + { + if (!em->enabled) continue; + if (!em->name) continue; + if (eina_streq(em->name, "bluez4")) + { + eina_stringshare_replace(&(em->name), "bluez5"); + } + } + EINA_LIST_FOREACH(e_config->gadcons, l, gc) + { + EINA_LIST_FOREACH(gc->clients, ll, gcc) + { + if (!gcc->name) continue; + if (!strncmp(gcc->name, "bluez4", 6)) + { + char *tmp = strdup(gcc->name); + if (tmp) + { + tmp[5] = '5'; + eina_stringshare_replace(&(gcc->name), tmp); + free(tmp); + } + } + } + } + CONFIG_VERSION_UPDATE_INFO(26); + e_config_save_queue(); + } } elm_config_profile_set(_e_config_profile); if (!e_config->remember_internal_fm_windows) diff --git a/src/bin/e_config.h b/src/bin/e_config.h index a4af30c5c..0ac67f144 100644 --- a/src/bin/e_config.h +++ b/src/bin/e_config.h @@ -46,7 +46,7 @@ typedef enum /* increment this whenever a new set of config values are added but the users * config doesn't need to be wiped - simply new values need to be put in */ -#define E_CONFIG_FILE_GENERATION 25 +#define E_CONFIG_FILE_GENERATION 26 #define E_CONFIG_FILE_VERSION ((E_CONFIG_FILE_EPOCH * 1000000) + E_CONFIG_FILE_GENERATION) #define E_CONFIG_BINDINGS_VERSION 0 // DO NOT INCREMENT UNLESS YOU WANT TO WIPE ALL BINDINGS!!!!! diff --git a/src/bin/e_module.c b/src/bin/e_module.c index 96ddcb67a..8c65f1185 100644 --- a/src/bin/e_module.c +++ b/src/bin/e_module.c @@ -997,6 +997,7 @@ _e_module_whitelist_check(void) "backlight", "battery", "bluez4", + "bluez5", "clock", "conf", "conf_applications", diff --git a/src/modules/bluez5/bz.c b/src/modules/bluez5/bz.c new file mode 100644 index 000000000..d0a7e083d --- /dev/null +++ b/src/modules/bluez5/bz.c @@ -0,0 +1,109 @@ +#include "e_mod_main.h" + +Eldbus_Connection *bz_conn = NULL; + +static Ecore_Timer *owner_gain_timer = NULL; + +///////////////////////////////////////////////////////////////////////////// + +static Eina_Bool +cb_name_owner_new(void *data EINA_UNUSED) +{ + owner_gain_timer = NULL; + bz_obj_init(); + bz_agent_init(); + return EINA_FALSE; +} + +static void +cb_name_owner_changed(void *data EINA_UNUSED, + const char *bus EINA_UNUSED, + const char *from EINA_UNUSED, + const char *to) +{ + static Eina_Bool first = EINA_TRUE; + if (to[0]) + { + if (owner_gain_timer) ecore_timer_del(owner_gain_timer); + // on first start try and re-init quickly because we get a name + // owner change even if all is good when we register to listen for it, + // so start fast + if (first) + owner_gain_timer = ecore_timer_add(0.1, cb_name_owner_new, NULL); + // but if we gegt a name owner change later it's probably because + // bluez was restarted or crashed. a new bz daemon will (or should) + // come up. so re-init more slowly here giving the daemon some time + // to come up before pestering it. + else + owner_gain_timer = ecore_timer_add(1.0, cb_name_owner_new, NULL); + first = EINA_FALSE; + } + else + { + if (owner_gain_timer) ecore_timer_del(owner_gain_timer); + owner_gain_timer = NULL; + ebluze5_popup_clear(); + bz_agent_shutdown(); + bz_obj_shutdown(); + } +} + +static void +cb_obj_add(Obj *o) +{ + if (o->type == BZ_OBJ_ADAPTER) + { + o->fn_change = ebluez5_popup_adapter_change; + o->fn_del = ebluez5_popup_adapter_del; + ebluez5_popup_adapter_add(o); + return; + } + if (o->type == BZ_OBJ_DEVICE) + { + o->fn_change = ebluez5_popup_device_change; + o->fn_del = ebluez5_popup_device_del; + ebluez5_popup_device_add(o); + return; + } +} + +void +bz_init(void) +{ + bz_conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM); + bz_obj_add_func_set(cb_obj_add); + bz_agent_release_func_set(ebluez5_agent_agent_release); + bz_agent_cancel_func_set(ebluez5_agent_agent_cancel); + bz_agent_req_pin_func_set(ebluez5_agent_agent_req_pin); + bz_agent_disp_pin_func_set(ebluez5_agent_agent_disp_pin); + bz_agent_req_pass_func_set(ebluez5_agent_req_pass); + bz_agent_disp_pass_func_set(ebluez5_agent_disp_pass); + bz_agent_req_confirm_func_set(ebluez5_agent_req_confirm); + bz_agent_req_auth_func_set(ebluez5_agent_req_auth); + bz_agent_auth_service_func_set(ebluez5_agent_auth_service); + eldbus_name_owner_changed_callback_add(bz_conn, "org.bluez", + cb_name_owner_changed, + NULL, EINA_TRUE); +} + +void +bz_shutdown(void) +{ + eldbus_name_owner_changed_callback_del(bz_conn, "org.bluez", + cb_name_owner_changed, NULL); + bz_agent_release_func_set(NULL); + bz_agent_cancel_func_set(NULL); + bz_agent_req_pin_func_set(NULL); + bz_agent_disp_pin_func_set(NULL); + bz_agent_req_pass_func_set(NULL); + bz_agent_disp_pass_func_set(NULL); + bz_agent_req_confirm_func_set(NULL); + bz_agent_req_auth_func_set(NULL); + bz_agent_auth_service_func_set(NULL); + bz_obj_add_func_set(NULL); + ebluze5_popup_clear(); + bz_agent_shutdown(); + bz_obj_shutdown(); + eldbus_connection_unref(bz_conn); + bz_conn = NULL; +} diff --git a/src/modules/bluez5/bz.h b/src/modules/bluez5/bz.h new file mode 100644 index 000000000..252f20180 --- /dev/null +++ b/src/modules/bluez5/bz.h @@ -0,0 +1,234 @@ +#ifndef BZ_H +# define BZ_H +# include + +typedef enum { + BZ_OBJ_UNKNOWN, + BZ_OBJ_BLUEZ, + BZ_OBJ_ADAPTER, + BZ_OBJ_DEVICE +} Obj_Type; + +typedef struct _Obj Obj; + +struct _Obj { + //// internal object data + Eldbus_Proxy *proxy; + Eldbus_Proxy *prop_proxy; + Eldbus_Signal_Handler *prop_sig; + unsigned int ref; + Eina_Bool in_table : 1; + Eina_Bool add_called : 1; + //// public data to read + const char *path; + Obj_Type type; + //// data to be set by users of the obj + void *data; // custom data + void (*fn_change) (Obj *o); + void (*fn_del) (Obj *o); + //// obj properties + Eina_Array *uuids; + const char *address; + const char *address_type; + const char *name; + const char *icon; + const char *alias; + const char *adapter; + const char *modalias; + unsigned int klass; + unsigned short appearance; + unsigned short txpower; + short rssi; + Eina_Bool paired : 1; + Eina_Bool connected : 1; + Eina_Bool trusted : 1; + Eina_Bool blocked : 1; + Eina_Bool legacy_pairing : 1; + Eina_Bool services_resolved : 1; + //// adapter specific properties + unsigned int discoverable_timeout; + unsigned int pairable_timeout; + Eina_Bool discoverable : 1; + Eina_Bool discovering : 1; + Eina_Bool pairable : 1; + Eina_Bool powered : 1; + //// agent data for when devices ask to pair etc. + const char *agent_request; + Eldbus_Message *agent_msg_ok; + Eldbus_Message *agent_msg_err; + void (*agent_entry_fn) (Eldbus_Message *msg, const char *str); + Eina_Bool agent_alert : 1; +}; + +#define BZ_OBJ_CLASS_SERVICE_LIMITED_DISCOVERABLE (1 << 13) +#define BZ_OBJ_CLASS_SERVICE_POSITIONING_BIT (1 << 16) +#define BZ_OBJ_CLASS_SERVICE_NETWORKING_BIT (1 << 17) +#define BZ_OBJ_CLASS_SERVICE_RENDERING_BIT (1 << 18) +#define BZ_OBJ_CLASS_SERVICE_CAPTURING_BIT (1 << 19) +#define BZ_OBJ_CLASS_SERVICE_OBJECT_TRANSFER_BIT (1 << 20) +#define BZ_OBJ_CLASS_SERVICE_AUDIO_BIT (1 << 21) +#define BZ_OBJ_CLASS_SERVICE_TELEPHONY_BIT (1 << 22) +#define BZ_OBJ_CLASS_SERVICE_INFORMATION_BIT (1 << 23) + +#define BZ_OBJ_CLASS_MAJ_MASK (0x1f << 8) +#define BZ_OBJ_CLASS_MAJ_MISC ( 0 << 8) +#define BZ_OBJ_CLASS_MAJ_COMPUTER ( 1 << 8) +#define BZ_OBJ_CLASS_MAJ_PHONE ( 2 << 8) +#define BZ_OBJ_CLASS_MAJ_LAN ( 3 << 8) +#define BZ_OBJ_CLASS_MAJ_AV ( 4 << 8) +#define BZ_OBJ_CLASS_MAJ_PERIPHERAL ( 5 << 8) +#define BZ_OBJ_CLASS_MAJ_IMAGING ( 6 << 8) +#define BZ_OBJ_CLASS_MAJ_WEARABLE ( 7 << 8) +#define BZ_OBJ_CLASS_MAJ_TOY ( 8 << 8) +#define BZ_OBJ_CLASS_MAJ_HEALTH ( 9 << 8) + +#define BZ_OBJ_CLASS_MIN_COMPUTER_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_DESKTOP ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_SERVER ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_LAPTOP ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_CLAMSHELL ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_PDA ( 5 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_WEARABLE ( 6 << 2) +#define BZ_OBJ_CLASS_MIN_COMPUTER_TABLET ( 7 << 2) + +#define BZ_OBJ_CLASS_MIN_PHONE_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_PHONE_CELL ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_PHONE_CORDLESS ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_PHONE_SMARTPHONE ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_PHONE_WIRED ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_PHONE_ISDN ( 5 << 2) + +#define BZ_OBJ_CLASS_MIN_LAN_MASK (0x07 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_7 ( 0 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_6 ( 1 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_5 ( 2 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_4 ( 3 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_3 ( 4 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_2 ( 5 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_1 ( 6 << 5) +#define BZ_OBJ_CLASS_MIN_LAN_AVAIL_0 ( 7 << 5) + +#define BZ_OBJ_CLASS_MIN_AV_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_AV_WEARABLE_HEADSET ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_AV_HANDS_FREE ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_AV_MIC ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_AV_SPEAKER ( 5 << 2) +#define BZ_OBJ_CLASS_MIN_AV_HEADPHONES ( 6 << 2) +#define BZ_OBJ_CLASS_MIN_AV_PORTABLE_AUDIO ( 7 << 2) +#define BZ_OBJ_CLASS_MIN_AV_CAR_AUDIO ( 8 << 2) +#define BZ_OBJ_CLASS_MIN_AV_SET_TOP_BOX ( 9 << 2) +#define BZ_OBJ_CLASS_MIN_AV_HIFI_AUDIO (10 << 2) +#define BZ_OBJ_CLASS_MIN_AV_VCR (11 << 2) +#define BZ_OBJ_CLASS_MIN_AV_VIDEO_CAMERA (12 << 2) +#define BZ_OBJ_CLASS_MIN_AV_CAMCORDER (13 << 2) +#define BZ_OBJ_CLASS_MIN_AV_VIDEO_MONITOR (14 << 2) +#define BZ_OBJ_CLASS_MIN_AV_VIDEO_DISPLAY_SPEAKER (15 << 2) +#define BZ_OBJ_CLASS_MIN_AV_VIDEO_CONFERENCE (16 << 2) +#define BZ_OBJ_CLASS_MIN_AV_GAMING (18 << 2) + +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_KEYBOARD_BIT ( 1 << 6) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_MOUSE_BIT ( 1 << 7) + +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_MASK2 (0x0f << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_JOYSTICK ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_GAMEPAD ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_REMOTE ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_SENSING ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_DIGITIZER_TAB ( 5 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_CARD_READER ( 6 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_PEN ( 7 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_SCANNER ( 8 << 2) +#define BZ_OBJ_CLASS_MIN_PERIPHERAL_WAND ( 9 << 2) + +#define BZ_OBJ_CLASS_MIN_IMAGING_DISPLAY_BIT ( 1 << 4) +#define BZ_OBJ_CLASS_MIN_IMAGING_CAMERA_BIT ( 1 << 5) +#define BZ_OBJ_CLASS_MIN_IMAGING_SCANNER_BIT ( 1 << 6) +#define BZ_OBJ_CLASS_MIN_IMAGING_PRINTER_BIT ( 1 << 7) + +#define BZ_OBJ_CLASS_MIN_WEARABLE_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_WEARABLE_WATCH ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_WEARABLE_PAGER ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_WEARABLE_JACKET ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_WEARABLE_HELMET ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_WEARABLE_GLASSES ( 5 << 2) + +#define BZ_OBJ_CLASS_MIN_TOY_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_TOY_ROBOT ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_TOY_VEHICLE ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_TOY_DOLL ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_TOY_CONTROLLER ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_TOY_GAME ( 5 << 2) + +#define BZ_OBJ_CLASS_MIN_HEALTH_MASK (0x3f << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_BLOOD_PRESSURE ( 1 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_THERMOMETER ( 2 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_SCALES ( 3 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_GLUCOSE ( 4 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_PULSE_OXIMITER ( 5 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_HEART_RATE ( 6 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_HEALTH_DATA_DISP ( 7 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_STEP ( 8 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_BODY_COMPOSITION ( 9 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_PEAK_FLOW (10 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_MEDICATION (11 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_KNEE_PROSTHESIS (12 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_ANKLE_PROSTHESIS (13 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_GENERIC_HEALTH (14 << 2) +#define BZ_OBJ_CLASS_MIN_HEALTH_PRESONAL_MOBILITY (15 << 2) + + +extern Eldbus_Connection *bz_conn; + +void bz_init(void); +void bz_shutdown(void); + +Obj *bz_obj_add(const char *path); +Obj *bz_obj_find(const char *path); +void bz_obj_power_on(Obj *o); +void bz_obj_power_off(Obj *o); +void bz_obj_discoverable(Obj *o); +void bz_obj_undiscoverable(Obj *o); +void bz_obj_pairable(Obj *o); +void bz_obj_unpairable(Obj *o); +void bz_obj_trust(Obj *o); +void bz_obj_distrust(Obj *o); +void bz_obj_pair(Obj *o); +void bz_obj_pair_cancel(Obj *o); +void bz_obj_connect(Obj *o); +void bz_obj_disconnect(Obj *o); +void bz_obj_remove(Obj *o); +void bz_obj_profile_connect(Obj *o, const char *uuid); +void bz_obj_profile_disconnect(Obj *o, const char *uuid); +void bz_obj_ref(Obj *o); +void bz_obj_unref(Obj *o); +void bz_obj_discover_start(Obj *o); +void bz_obj_discover_stop(Obj *o); +void bz_obj_agent_request(Obj *o, const char *req, void (*fn) (Eldbus_Message *msg, const char *str), Eldbus_Message *ok_msg, Eldbus_Message *err_msg); + +void bz_obj_init(void); +void bz_obj_shutdown(void); +void bz_obj_add_func_set(void (*fn) (Obj *o)); + +void bz_agent_msg_reply(Eldbus_Message *msg); +void bz_agent_msg_drop(Eldbus_Message *msg); +Eldbus_Message *bz_agent_msg_err(Eldbus_Message *msg); +Eldbus_Message *bz_agent_msg_ok(Eldbus_Message *msg); +const char *bz_agent_msg_path(Eldbus_Message *msg); +const char *bz_agent_msg_path_str(Eldbus_Message *msg, const char **str); +const char *bz_agent_msg_path_u32(Eldbus_Message *msg, unsigned int *u32); +const char *bz_agent_msg_path_u32_u16(Eldbus_Message *msg, unsigned int *u32, unsigned short *u16); +void bz_agent_msg_str_add(Eldbus_Message *msg, const char *str); +void bz_agent_msg_u32_add(Eldbus_Message *msg, unsigned int u32); + +void bz_agent_init(void); +void bz_agent_shutdown(void); +void bz_agent_release_func_set(void (*fn) (void)); +void bz_agent_cancel_func_set(void (*fn) (void)); +void bz_agent_req_pin_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_disp_pin_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_req_pass_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_disp_pass_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_req_confirm_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_req_auth_func_set(void (*fn) (Eldbus_Message *msg)); +void bz_agent_auth_service_func_set(void (*fn) (Eldbus_Message *msg)); +#endif diff --git a/src/modules/bluez5/bz_agent.c b/src/modules/bluez5/bz_agent.c new file mode 100644 index 000000000..8eb0489cb --- /dev/null +++ b/src/modules/bluez5/bz_agent.c @@ -0,0 +1,346 @@ +#include "bz.h" +#include "e.h" + +static Eldbus_Service_Interface *agent_iface = NULL; +static Eldbus_Proxy *agent_proxy = NULL; +static void (*fn_release) (void) = NULL; +static void (*fn_cancel) (void) = NULL; +static void (*fn_req_pin) (Eldbus_Message *msg) = NULL; +static void (*fn_disp_pin) (Eldbus_Message *msg) = NULL; +static void (*fn_req_pass) (Eldbus_Message *msg) = NULL; +static void (*fn_disp_pass) (Eldbus_Message *msg) = NULL; +static void (*fn_req_confirm) (Eldbus_Message *msg) = NULL; +static void (*fn_req_auth) (Eldbus_Message *msg) = NULL; +static void (*fn_auth_service) (Eldbus_Message *msg) = NULL; + +static Eldbus_Message * +cb_agent_release(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + Eldbus_Message *reply = eldbus_message_method_return_new(msg); + + if (fn_release) fn_release(); + return reply; +} + +static Eldbus_Message * +cb_agent_cancel(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + Eldbus_Message *reply = eldbus_message_method_return_new(msg); + + if (fn_cancel) fn_cancel(); + return reply; +} + +static Eldbus_Message * +cb_agent_request_pin_code(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_req_pin) fn_req_pin((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_display_pin_code(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_disp_pin) fn_disp_pin((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_request_pass_key(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_req_pass) fn_req_pass((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_display_pass_key(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_disp_pass) fn_disp_pass((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_request_confirmation(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_req_confirm) fn_req_confirm((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_request_authorization(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_req_auth) fn_req_auth((Eldbus_Message *)msg); + return NULL; +} + +static Eldbus_Message * +cb_agent_authorize_service(const Eldbus_Service_Interface *iface EINA_UNUSED, + const Eldbus_Message *msg) +{ + if (fn_auth_service) fn_auth_service((Eldbus_Message *)msg); + return NULL; +/* Not done yet... don't even know what services are with bt... + Eldbus_Message *reply = eldbus_message_method_return_new(msg); + const char *path = NULL, *uuid = NULL; + + printf("Agent authorize service\n"); + if (!eldbus_message_arguments_get(msg, "os", &path, &uuid)) return reply; + printf(" %s %s\n", path, uuid); + // if ok return this reply, or make it an error... + // reply = eldbus_message_error_new(msg, "org.bluez.Error.Rejected", ""); + return reply; + // really return NULL and defer above reply with: + // eldbus_connection_send(bz_conn, reply, NULL, NULL, -1); + */ +} + +static void +cb_default(void *data EINA_UNUSED, const Eldbus_Message *msg, + Eldbus_Pending *pending EINA_UNUSED) +{ + const char *name, *text; + + if (eldbus_message_error_get(msg, &name, &text)) + { + // XXX: should have a visual e log... + e_util_dialog_show(_("Bluetooth"), + _("Could not register default agent:
%s %s"), + name, text); + return; + } +} + +static void +cb_register(void *data EINA_UNUSED, const Eldbus_Message *msg, + Eldbus_Pending *pending EINA_UNUSED) +{ + const char *name, *text; + + if (eldbus_message_error_get(msg, &name, &text)) + { + // XXX: should have a visual e log... + e_util_dialog_show(_("Bluetooth"), + _("Could not register agent:
%s %s\n"), + name, text); + return; + } + if (!agent_proxy) return; + eldbus_proxy_call(agent_proxy, "RequestDefaultAgent", + cb_default, NULL, -1, + "o", "/org/enlightenment/bluez5/agent"); +} + +static void +cb_unregister(void *data EINA_UNUSED, const Eldbus_Message *msg, + Eldbus_Pending *pending EINA_UNUSED) +{ + const char *name, *text; + + if (agent_proxy) + { + eldbus_proxy_unref(agent_proxy); + agent_proxy = NULL; + } + if (agent_iface) + { + eldbus_service_object_unregister(agent_iface); + agent_iface = NULL; + } + if (eldbus_message_error_get(msg, &name, &text)) + { + // just debug for developers + printf("Could not unregister agent.\n %s:\n %s\n", name, text); + return; + } +} + +static const Eldbus_Method agent_methods[] = { + { "Release", NULL, NULL, cb_agent_release, 0 }, + { "RequestPinCode", ELDBUS_ARGS({ "o", "device" }), ELDBUS_ARGS({ "s", "pincode" }), cb_agent_request_pin_code, 0 }, + { "DisplayPinCode", ELDBUS_ARGS({ "o", "device" }, { "s", "pincode" }), NULL, cb_agent_display_pin_code, 0 }, + { "RequestPasskey", ELDBUS_ARGS({ "o", "device" }), ELDBUS_ARGS({ "u", "passkey" }), cb_agent_request_pass_key, 0 }, + { "DisplayPasskey", ELDBUS_ARGS({ "o", "device" }, { "u", "passkey" }, { "q", "entered" }), NULL, cb_agent_display_pass_key, 0 }, + { "RequestConfirmation", ELDBUS_ARGS({ "o", "device" }, { "u", "passkey" }), NULL, cb_agent_request_confirmation, 0 }, + { "RequestAuthorization", ELDBUS_ARGS({ "o", "device" }), NULL, cb_agent_request_authorization, 0 }, + { "AuthorizeService", ELDBUS_ARGS({ "o", "device" }, { "s", "uuid" }), NULL, cb_agent_authorize_service, 0 }, + { "Cancel", NULL, NULL, cb_agent_cancel, 0 }, + + { NULL, NULL, NULL, NULL, 0 } +}; +static const Eldbus_Service_Interface_Desc agent_desc = { + "org.bluez.Agent1", agent_methods, NULL, NULL, NULL, NULL +}; + +void +bz_agent_msg_reply(Eldbus_Message *msg) +{ + if (!bz_conn) + { + eldbus_message_unref(msg); + return; + } + eldbus_connection_send(bz_conn, msg, NULL, NULL, -1); +} + +void +bz_agent_msg_drop(Eldbus_Message *msg) +{ + eldbus_message_unref(msg); +} + +Eldbus_Message * +bz_agent_msg_err(Eldbus_Message *msg) +{ + return eldbus_message_error_new(msg, "org.bluez.Error.Rejected", ""); +} + +Eldbus_Message * +bz_agent_msg_ok(Eldbus_Message *msg) +{ + return eldbus_message_method_return_new(msg); +} + +const char * +bz_agent_msg_path(Eldbus_Message *msg) +{ + const char *s = NULL; + + if (!eldbus_message_arguments_get(msg, "o", &s)) return NULL; + return s; +} + +const char * +bz_agent_msg_path_str(Eldbus_Message *msg, const char **str) +{ + const char *s = NULL, *s2 = NULL; + + if (!eldbus_message_arguments_get(msg, "os", &s, &s2)) return NULL; + if (str) *str = s2; + return s; +} + +const char * +bz_agent_msg_path_u32(Eldbus_Message *msg, unsigned int *u32) +{ + const char *s = NULL; + unsigned int uu32 = 0; + + if (!eldbus_message_arguments_get(msg, "ou", &s, &uu32)) return NULL; + if (u32) *u32 = uu32; + return s; +} + +const char * +bz_agent_msg_path_u32_u16(Eldbus_Message *msg, unsigned int *u32, unsigned short *u16) +{ + const char *s = NULL; + unsigned int uu32 = 0; + unsigned short uu16 = 0; + + if (!eldbus_message_arguments_get(msg, "ouq", &s, &uu32, &uu16)) return NULL; + if (u32) *u32 = uu32; + if (u16) *u16 = uu16; + return s; +} + +void +bz_agent_msg_str_add(Eldbus_Message *msg, const char *str) +{ + eldbus_message_arguments_append(msg, "s", str); +} + +void +bz_agent_msg_u32_add(Eldbus_Message *msg, unsigned int u32) +{ + eldbus_message_arguments_append(msg, "u", &u32); +} + +void +bz_agent_init(void) +{ + Eldbus_Object *obj; + + obj = eldbus_object_get(bz_conn, "org.bluez", "/org/bluez"); + agent_proxy = eldbus_proxy_get(obj, "org.bluez.AgentManager1"); + agent_iface = eldbus_service_interface_register + (bz_conn, "/org/enlightenment/bluez5/agent", &agent_desc); + if (agent_proxy) + eldbus_proxy_call(agent_proxy, "RegisterAgent", + cb_register, NULL, -1, + "os", "/org/enlightenment/bluez5/agent", + "KeyboardDisplay"); + else + e_util_dialog_show(_("Bluetooth"), + _("Could not call RegisterAgent\n")); +} + +void +bz_agent_shutdown(void) +{ + if (!agent_proxy) return; + eldbus_proxy_call(agent_proxy, "UnregisterAgent", + cb_unregister, NULL, -1, + "o", "/org/enlightenment/bluez5/agent"); +} + +void +bz_agent_release_func_set(void (*fn) (void)) +{ + fn_release = fn; +} + +void +bz_agent_cancel_func_set(void (*fn) (void)) +{ + fn_cancel = fn; +} + +void +bz_agent_req_pin_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_req_pin = fn; +} + +void +bz_agent_disp_pin_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_disp_pin = fn; +} + +void +bz_agent_req_pass_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_req_pass = fn; +} + +void +bz_agent_disp_pass_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_disp_pass = fn; +} + +void +bz_agent_req_confirm_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_req_confirm = fn; +} + +void +bz_agent_req_auth_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_req_auth = fn; +} + +void +bz_agent_auth_service_func_set(void (*fn) (Eldbus_Message *msg)) +{ + fn_auth_service = fn; +} diff --git a/src/modules/bluez5/bz_obj.c b/src/modules/bluez5/bz_obj.c new file mode 100644 index 000000000..24552bb2b --- /dev/null +++ b/src/modules/bluez5/bz_obj.c @@ -0,0 +1,760 @@ +#include "bz.h" + +static Eldbus_Proxy *objman_proxy = NULL; +static Eldbus_Signal_Handler *sig_ifadd = NULL; +static Eldbus_Signal_Handler *sig_ifdel = NULL; +static Eldbus_Pending *pend_getobj = NULL; +static Eina_Hash *obj_table = NULL; +static void (*fn_obj_add) (Obj *o) = NULL; + +/* +static void +cb_obj_prop_mandata(void *data, const void *key, Eldbus_Message_Iter *var) +{ + Obj *o = data; + unsigned short *skey = key; + + printf(" M KEY %x\n", (int)*skey); +} +*/ + +static void +cb_obj_prop_entry(void *data, const void *key, Eldbus_Message_Iter *var) +{ + Obj *o = data; + const char *skey = key; + + if (!strcmp(skey, "Paired")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->paired = val; + } + if (!strcmp(skey, "Connected")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->connected = val; + } + if (!strcmp(skey, "Trusted")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->trusted = val; + } + if (!strcmp(skey, "Blocked")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->blocked = val; + } + if (!strcmp(skey, "LegacyPairing")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->legacy_pairing = val; + } + if (!strcmp(skey, "ServicesResolved")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->services_resolved = val; + } + if (!strcmp(skey, "Address")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->address = eina_stringshare_add(val); + } + if (!strcmp(skey, "AddressType")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->address_type = eina_stringshare_add(val); + } + if (!strcmp(skey, "Name")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->name = eina_stringshare_add(val); + } + if (!strcmp(skey, "Icon")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->icon = eina_stringshare_add(val); + } + if (!strcmp(skey, "Alias")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->alias = eina_stringshare_add(val); + } + if (!strcmp(skey, "Modalias")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "s", &val)) + o->modalias = eina_stringshare_add(val); + } + if (!strcmp(skey, "Adapter")) + { + const char *val = NULL; + if (eldbus_message_iter_arguments_get(var, "o", &val)) + o->adapter = eina_stringshare_add(val); + } + if (!strcmp(skey, "Class")) + { + unsigned int val = 0; + if (eldbus_message_iter_arguments_get(var, "u", &val)) + o->klass = val; + } + if (!strcmp(skey, "Appearance")) + { + unsigned short val = 0; + if (eldbus_message_iter_arguments_get(var, "q", &val)) + o->appearance = val; + } + if (!strcmp(skey, "RSSI")) + { + short val = 0; + if (eldbus_message_iter_arguments_get(var, "n", &val)) + o->rssi = val; + } + if (!strcmp(skey, "TxPower")) + { + unsigned short val = 0; + if (eldbus_message_iter_arguments_get(var, "n", &val)) + o->txpower = val; + } + if (!strcmp(skey, "UUIDs")) + { + Eldbus_Message_Iter *array = NULL; + + if (eldbus_message_iter_arguments_get(var, "as", &array)) + { + const char *val = NULL; + + while (eldbus_message_iter_get_and_next(array, 's', &val)) + { + if (!o->uuids) o->uuids = eina_array_new(1); + eina_array_push(o->uuids, eina_stringshare_add(val)); + } + } + } + if (!strcmp(skey, "Discoverable")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->discoverable = val; + } + if (!strcmp(skey, "Discovering")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->discovering = val; + } + if (!strcmp(skey, "Pairable")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->pairable = val; + } + if (!strcmp(skey, "Powered")) + { + Eina_Bool val = EINA_FALSE; + if (eldbus_message_iter_arguments_get(var, "b", &val)) + o->powered = val; + } + if (!strcmp(skey, "DiscoverableTimeout")) + { + unsigned int val = 0; + if (eldbus_message_iter_arguments_get(var, "u", &val)) + o->discoverable_timeout = val; + } + if (!strcmp(skey, "PairableTimeout")) + { + unsigned int val = 0; + if (eldbus_message_iter_arguments_get(var, "u", &val)) + o->pairable_timeout = val; + } + // dict ManufacturerData [readonly, optional] + // Manufacturer specific advertisement data. Keys are + // 16 bits Manufacturer ID followed by its byte array + // value. +/* + if (!strcmp(skey, "ManufacturerData")) + { + Eldbus_Message_Iter *array = NULL; + + if (eldbus_message_iter_arguments_get(var, "a{qv}", &array)) + eldbus_message_iter_dict_iterate(array, "qv", cb_obj_prop_mandata, o); + } + */ + // dict ServiceData [readonly, optional] + // Service advertisement data. Keys are the UUIDs in + // string format followed by its byte array value. + // + // array{byte} AdvertisingFlags [readonly, experimental] + // The Advertising Data Flags of the remote device. + // + // dict AdvertisingData [readonly, experimental] + // The Advertising Data of the remote device. Keys are + // are 8 bits AD Type followed by data as byte array. +} + +static void +_obj_clear(Obj *o) +{ + o->paired = EINA_FALSE; + o->connected = EINA_FALSE; + o->trusted = EINA_FALSE; + o->blocked = EINA_FALSE; + o->legacy_pairing = EINA_FALSE; + o->services_resolved = EINA_FALSE; + eina_stringshare_del(o->address); + o->address = NULL; + eina_stringshare_del(o->address_type); + o->address_type = NULL; + eina_stringshare_del(o->name); + o->name = NULL; + eina_stringshare_del(o->icon); + o->icon = NULL; + eina_stringshare_del(o->alias); + o->alias = NULL; + eina_stringshare_del(o->adapter); + o->adapter = NULL; + eina_stringshare_del(o->modalias); + o->modalias = NULL; + eina_stringshare_del(o->modalias); + o->modalias = NULL; + o->klass = 0; + o->appearance = 0; + o->txpower = 0; + o->rssi = 0; + if (o->uuids) + { + const char *val; + + while ((val = eina_array_pop(o->uuids))) + eina_stringshare_del(val); + eina_array_free(o->uuids); + o->uuids = NULL; + } +} + +#define ERR_PRINT(str) \ + do { const char *name, *text; \ + if (eldbus_message_error_get(msg, &name, &text)) { \ + printf("Error: %s.\n %s:\n %s\n", str, name, text); \ + return; \ + } \ + } while(0) + + +static void +cb_obj_prop(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) +{ + Obj *o = data; + Eldbus_Message_Iter *array; + + if (eldbus_message_error_get(msg, NULL, NULL)) return; + _obj_clear(o); + if (eldbus_message_arguments_get(msg, "a{sv}", &array)) + eldbus_message_iter_dict_iterate(array, "sv", cb_obj_prop_entry, o); + bz_obj_ref(o); + if (!o->add_called) + { + o->add_called = EINA_TRUE; + if (fn_obj_add) fn_obj_add(o); + } + if (o->fn_change) o->fn_change(o); + bz_obj_unref(o); +} + +static void +cb_obj_prop_changed(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED) +{ + Obj *o = data; + if (!o->proxy) return; + eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o); +} + +Obj * +bz_obj_add(const char *path) +{ + Eldbus_Object *obj; + + Obj *o = calloc(1, sizeof(Obj)); + o->ref = 1; + o->path = eina_stringshare_add(path); + obj = eldbus_object_get(bz_conn, "org.bluez", o->path); + o->type = BZ_OBJ_UNKNOWN; + o->in_table = EINA_TRUE; + eina_hash_add(obj_table, o->path, o); + if (!strcmp(o->path, "/org/bluez")) + { + o->proxy = eldbus_proxy_get(obj, "org.bluez.AgentManager1"); + o->type = BZ_OBJ_BLUEZ; + o->add_called = EINA_TRUE; + bz_obj_ref(o); + if (fn_obj_add) fn_obj_add(o); + bz_obj_unref(o); + goto done; + } + // all devices are /org/bluez/XXX/dev_XXX so look for /dev_ + else if (strstr(o->path, "/dev_")) + { + o->proxy = eldbus_proxy_get(obj, "org.bluez.Device1"); + o->type = BZ_OBJ_DEVICE; + if (o->proxy) + { + eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o); + o->prop_proxy = eldbus_proxy_get(obj, + "org.freedesktop.DBus.Properties"); + if (o->prop_proxy) + o->prop_sig = eldbus_proxy_signal_handler_add(o->prop_proxy, + "PropertiesChanged", + cb_obj_prop_changed, o); + } + goto done; + } + // all dadapters begin with /org/bluez/ + else if (!strncmp(o->path, "/org/bluez/", 11)) + { + o->proxy = eldbus_proxy_get(obj, "org.bluez.Adapter1"); + o->type = BZ_OBJ_ADAPTER; + if (o->proxy) + { + eldbus_proxy_property_get_all(o->proxy, cb_obj_prop, o); + o->prop_proxy = eldbus_proxy_get(obj, + "org.freedesktop.DBus.Properties"); + if (o->prop_proxy) + o->prop_sig = eldbus_proxy_signal_handler_add(o->prop_proxy, + "PropertiesChanged", + cb_obj_prop_changed, o); + } + goto done; + } +done: + return o; +} + +Obj * +bz_obj_find(const char *path) +{ + return eina_hash_find(obj_table, path); +} + +static void +cb_power_on(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Power On"); +} + +void +bz_obj_power_on(Obj *o) +{ + Eina_Bool val = EINA_TRUE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Powered", "b", (void *)(uintptr_t)val, cb_power_on, o); +} + +static void +cb_power_off(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Power Off"); +} + +void +bz_obj_power_off(Obj *o) +{ + Eina_Bool val = EINA_FALSE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Powered", "b", (void *)(uintptr_t)val, cb_power_off, o); +} + +static void +cb_discoverable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Discoverable"); +} + +void +bz_obj_discoverable(Obj *o) +{ + Eina_Bool val = EINA_TRUE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Discoverable", "b", (void *)(uintptr_t)val, cb_discoverable, o); +} + +static void +cb_undiscoverable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Undiscoverable"); +} + +void +bz_obj_undiscoverable(Obj *o) +{ + Eina_Bool val = EINA_FALSE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Discoverable", "b", (void *)(uintptr_t)val, cb_undiscoverable, o); +} + +static void +cb_pairable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Pairable"); +} + +void +bz_obj_pairable(Obj *o) +{ + Eina_Bool val = EINA_TRUE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Pairable", "b", (void *)(uintptr_t)val, cb_pairable, o); +} + +static void +cb_unpairable(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Unpairable"); +} + +void +bz_obj_unpairable(Obj *o) +{ + Eina_Bool val = EINA_FALSE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Pairable", "b", (void *)(uintptr_t)val, cb_unpairable, o); +} + +static void +cb_trust(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Trust"); +} + +void +bz_obj_trust(Obj *o) +{ + Eina_Bool val = EINA_TRUE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Trusted", "b", (void *)(uintptr_t)val, cb_trust, o); +} + +static void +cb_distrust(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Distrust"); +} + +void +bz_obj_distrust(Obj *o) +{ + Eina_Bool val = EINA_FALSE; + if (!o->proxy) return; + eldbus_proxy_property_set + (o->proxy, "Trusted", "b", (void *)(uintptr_t)val, cb_distrust, o); +} + +static void +cb_pair(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Pair"); +} + +void +bz_obj_pair(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call(o->proxy, "Pair", cb_pair, o, -1, ""); +} + +static void +cb_pair_cancel(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Pair Cancel"); +} + +void +bz_obj_pair_cancel(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call + (o->proxy, "CancelPairing", cb_pair_cancel, o, -1, ""); +} + +static void +cb_connect(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Connect"); +} + +void +bz_obj_connect(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call + (o->proxy, "Connect", cb_connect, o, -1, ""); +} + +static void +cb_disconnect(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Disconnect"); +} + +void +bz_obj_disconnect(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call + (o->proxy, "Disconnect", cb_disconnect, o, -1, ""); +} +/* +void +bz_obj_profile_connect(Obj *o, const char *uuid) +{ + if (!o->proxy) return; + eldbus_proxy_call(o->proxy, "ConnectProfile", NULL, NULL, -1, "s", uuid); +} + +void +bz_obj_profile_disconnect(Obj *o, const char *uuid) +{ + if (!o->proxy) return; + eldbus_proxy_call(o->proxy, "DisconnectProfile", NULL, NULL, -1, "s", uuid); +} +*/ +static void +cb_remove(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Remove"); +} + +void +bz_obj_remove(Obj *o) +{ + if (o->adapter) + { + Obj *adapter = bz_obj_find(o->adapter); + if (adapter) + { + if (!adapter->proxy) return; + eldbus_proxy_call(adapter->proxy, "RemoveDevice", + cb_remove, adapter, -1, + "o", o->path); + } + } +} + +void +bz_obj_ref(Obj *o) +{ + o->ref++; +} + +void +bz_obj_unref(Obj *o) +{ + o->ref--; + if (o->ref > 0) return; + if (o->in_table) + { + o->in_table = EINA_FALSE; + eina_hash_del(obj_table, o->path, o); + } + _obj_clear(o); + if (o->prop_sig) + { + eldbus_signal_handler_del(o->prop_sig); + o->prop_sig = NULL; + } + if (o->proxy) + { + eldbus_proxy_unref(o->proxy); + o->proxy = NULL; + } + if (o->prop_proxy) + { + eldbus_proxy_unref(o->prop_proxy); + o->prop_proxy = NULL; + } + if (o->path) + { + eina_stringshare_del(o->path); + o->path = NULL; + } + if (o->agent_request) + { + eina_stringshare_del(o->agent_request); + o->agent_request = NULL; + } + if (o->agent_msg_err) + { + bz_agent_msg_drop(o->agent_msg_err); + o->agent_msg_err = NULL; + } + if (o->agent_msg_ok) + { + bz_agent_msg_drop(o->agent_msg_ok); + o->agent_msg_ok = NULL; + } + free(o); +} + +static void +cb_discovery_start(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Discovery Start"); +} + +void +bz_obj_discover_start(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call + (o->proxy, "StartDiscovery", cb_discovery_start, o, -1, ""); +} + +static void +cb_discovery_stop(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) +{ + ERR_PRINT("Discovery Stop"); +} + +void +bz_obj_discover_stop(Obj *o) +{ + if (!o->proxy) return; + eldbus_proxy_call + (o->proxy, "StopDiscovery", cb_discovery_stop, o, -1, ""); +} + +void +bz_obj_agent_request(Obj *o, const char *req, void (*fn) (Eldbus_Message *msg, const char *str), Eldbus_Message *msg_ok, Eldbus_Message *msg_err) +{ + if (o->agent_msg_ok) bz_agent_msg_drop(o->agent_msg_ok); + if (o->agent_msg_err) bz_agent_msg_reply(o->agent_msg_err); + o->agent_msg_ok = msg_ok; + o->agent_msg_err = msg_err; + o->agent_entry_fn = fn; + o->agent_alert = EINA_TRUE; + eina_stringshare_replace(&(o->agent_request), req); + bz_obj_ref(o); + if (o->fn_change) o->fn_change(o); + bz_obj_unref(o); +} + +static void +cb_obj_add(void *data EINA_UNUSED, const Eldbus_Message *msg) +{ + const char *path = NULL; + + if (!eldbus_message_arguments_get(msg, "o", &path)) return; + if (bz_obj_find(path)) return; + bz_obj_add(path); +} + +static void +cb_obj_del(void *data EINA_UNUSED, const Eldbus_Message *msg) +{ + Obj *o; + const char *path = NULL; + + if (!eldbus_message_arguments_get(msg, "o", &path)) return; + o = bz_obj_find(path); + if (o) + { + bz_obj_ref(o); + if (o->fn_del) o->fn_del(o); + bz_obj_unref(o); + bz_obj_unref(o); + } +} + +static void +cb_getobj(void *data EINA_UNUSED, const Eldbus_Message *msg, + Eldbus_Pending *pending EINA_UNUSED) +{ + Eldbus_Message_Iter *prop, *dict; + + pend_getobj = NULL; + ERR_PRINT("Get Objects"); + if (eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &prop)) + { + while (eldbus_message_iter_get_and_next(prop, 'e', &dict)) + { + const char *path; + + path = NULL; + if (!eldbus_message_iter_arguments_get(dict, "o", &path)) + { + return; + } + bz_obj_add(path); + } + } +} + +static void +_obj_hash_free(Obj *o) +{ + o->in_table = 0; + bz_obj_unref(o); +} + +void +bz_obj_init(void) +{ + Eldbus_Object *obj; + + obj_table = eina_hash_string_superfast_new((void *)_obj_hash_free); + + obj = eldbus_object_get(bz_conn, "org.bluez", "/"); + objman_proxy = eldbus_proxy_get(obj, "org.freedesktop.DBus.ObjectManager"); + sig_ifadd = eldbus_proxy_signal_handler_add(objman_proxy, "InterfacesAdded", + cb_obj_add, NULL); + sig_ifdel = eldbus_proxy_signal_handler_add(objman_proxy, "InterfacesRemoved", + cb_obj_del, NULL); + pend_getobj = eldbus_proxy_call(objman_proxy, "GetManagedObjects", + cb_getobj, NULL, -1, ""); +} + +void +bz_obj_shutdown(void) +{ + eina_hash_free(obj_table); + obj_table = NULL; + if (pend_getobj) + { + eldbus_pending_cancel(pend_getobj); + pend_getobj = NULL; + } + if (sig_ifadd) + { + eldbus_signal_handler_del(sig_ifadd); + sig_ifadd = NULL; + } + if (sig_ifdel) + { + eldbus_signal_handler_del(sig_ifdel); + sig_ifdel = NULL; + } + eldbus_proxy_unref(objman_proxy); + objman_proxy = NULL; +} + +void +bz_obj_add_func_set(void (*fn) (Obj *o)) +{ + fn_obj_add = fn; +} diff --git a/src/modules/bluez5/e-module-bluez5.edj b/src/modules/bluez5/e-module-bluez5.edj new file mode 100644 index 0000000000000000000000000000000000000000..383b4460ed4c5aea9f55972c6b5f70e72919cd63 GIT binary patch literal 59845 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 literal 0 HcmV?d00001 diff --git a/src/modules/bluez5/e_mod_agent.c b/src/modules/bluez5/e_mod_agent.c new file mode 100644 index 000000000..a5524ad11 --- /dev/null +++ b/src/modules/bluez5/e_mod_agent.c @@ -0,0 +1,143 @@ +#include "e_mod_main.h" + +///////////////////////////////////////////////////////////////////////////// + +static void +cb_get_pass(Eldbus_Message *msg, const char *str) +{ + if (!str) return; + bz_agent_msg_str_add(msg, str); +} + +static void +cb_get_pin(Eldbus_Message *msg, const char *str) +{ + unsigned int pin; + if (!str) return; + pin = atoi(str); + bz_agent_msg_u32_add(msg, pin); +} + +void +ebluez5_agent_agent_release(void) +{ + // just debugging + printf("BZ5 Agent Release\n"); +} + +void +ebluez5_agent_agent_cancel(void) +{ + // just debugging + printf("BZ5 Agent Cancel\n"); +} + +void +ebluez5_agent_agent_req_pin(Eldbus_Message *msg) +{ + const char *path; + Obj *o; + + if (!(path = bz_agent_msg_path(msg))) goto error; + if (!(o = bz_obj_find(path))) goto error; + bz_obj_agent_request(o, _("Supply PIN"), cb_get_pass, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_agent_disp_pin(Eldbus_Message *msg) +{ + const char *path, *s = ""; + Obj *o; + char buf[1024]; + + if (!(path = bz_agent_msg_path_str(msg, &s))) goto error; + if (!(o = bz_obj_find(path))) goto error; + snprintf(buf, sizeof(buf), _("Pair? PIN: %s"), s); + bz_obj_agent_request(o, buf, NULL, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_req_pass(Eldbus_Message *msg) +{ + const char *path; + Obj *o; + + if (!(path = bz_agent_msg_path(msg))) goto error; + if (!(o = bz_obj_find(path))) goto error; + bz_obj_agent_request(o, _("Enter PIN"), cb_get_pin, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_disp_pass(Eldbus_Message *msg) +{ + const char *path; + unsigned int pin = 0; + unsigned short entered = 0; + Obj *o; + char buf[1024]; + + if (!(path = bz_agent_msg_path_u32_u16(msg, &pin, &entered))) goto error; + if (!(o = bz_obj_find(path))) goto error; + snprintf(buf, sizeof(buf), _("Pair? PIN: %06u"), pin); + bz_obj_agent_request(o, buf, NULL, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_req_confirm(Eldbus_Message *msg) +{ + const char *path; + unsigned int pin = 0; + Obj *o; + char buf[1024]; + + if (!(path = bz_agent_msg_path_u32(msg, &pin))) goto error; + if (!(o = bz_obj_find(path))) goto error; + snprintf(buf, sizeof(buf), _("Pair? PIN: %06u"), pin); + bz_obj_agent_request(o, buf, NULL, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_req_auth(Eldbus_Message *msg) +{ + const char *path; + Obj *o; + + if (!(path = bz_agent_msg_path(msg))) goto error; + if (!(o = bz_obj_find(path))) goto error; + bz_obj_agent_request(o, _("Connect?"), NULL, + bz_agent_msg_ok(msg), bz_agent_msg_err(msg)); + return; +error: + bz_agent_msg_reply(bz_agent_msg_err(msg)); +} + +void +ebluez5_agent_auth_service(Eldbus_Message *msg) +{ + // if "ask for service" on then ask, otherwise always auth + // always auth: +// if (!(path = bz_agent_msg_path_str(msg, &s))) goto error; + Eldbus_Message *ok; + ok = bz_agent_msg_ok(msg); + bz_agent_msg_reply(ok); +} diff --git a/src/modules/bluez5/e_mod_main.c b/src/modules/bluez5/e_mod_main.c new file mode 100644 index 000000000..6d779e0ea --- /dev/null +++ b/src/modules/bluez5/e_mod_main.c @@ -0,0 +1,305 @@ +#include "e_mod_main.h" + +static Eina_List *instances = NULL; +static E_Module *mod = NULL; + +/* Local config */ +static E_Config_DD *conf_edd = NULL; +Config *ebluez5_config = NULL; + +E_API E_Module_Api e_modapi = {E_MODULE_API_VERSION, "Bluez5"}; + +static void +_mod_icon_set(Evas_Object *base, Eina_Bool gadget) +{ + char edj_path[4096], *group; + + // XXX: hack for now until we make the icon do things and have it + // in theme in efl + snprintf(edj_path, sizeof(edj_path), "%s/e-module-bluez5.edj", mod->dir); + if (1) group = "e/modules/bluez5/main"; + else group = "e/modules/bluez5/inactive"; + + if (!e_theme_edje_object_set(base, "base/theme/modules/bluez5", group)) + { + if (gadget) + elm_layout_file_set(base, edj_path, group); + else + edje_object_file_set(base, edj_path, group); + } +} + +///////////////////////////////////////////////////////////////////////////// + +static void +_gad_popup_dismiss(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + E_FREE_FUNC(obj, evas_object_del); + inst->pop = NULL; +} + +static void +_gad_popup_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + inst->pop = NULL; +} + +static void +_gad_popup_do(Instance *inst) +{ + Evas_Object *o; + + if (inst->pop) return; + + inst->pop = o = elm_ctxpopup_add(e_comp->elm); + elm_object_style_set(o, "noblock"); + evas_object_smart_callback_add(o, "dismissed", _gad_popup_dismiss, inst); + evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _gad_popup_del, inst); + + inst->popcontent = o = ebluez5_popup_content_add(e_comp->elm, inst); + elm_object_content_set(inst->pop, o); + evas_object_show(o); + + e_gadget_util_ctxpopup_place(inst->o_bluez5, inst->pop, inst->o_bluez5); + evas_object_show(inst->pop); +} + +static void +_gad_mouse_down(void *data, Evas *evas EINA_UNUSED, + Evas_Object *obj EINA_UNUSED, void *event) +{ + Instance *inst = data; + Evas_Event_Mouse_Down *ev = event; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return; + if (ev->button != 1) return; + if (!inst->pop) _gad_popup_do(inst); + else elm_ctxpopup_dismiss(inst->pop); +} + +static void +_gad_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + + instances = eina_list_remove(instances, inst); + E_FREE(inst); +} + +/* XXX: fill in later when we have gotten this far +static Evas_Object * +_gad_config(Evas_Object *g EINA_UNUSED) +{ + if (e_configure_registry_exists("extensions/bluez5")) + e_configure_registry_call("extensions/bluez5", NULL, NULL); + return NULL; +} +*/ + +static Evas_Object * +_gad_create(Evas_Object *parent, int *id, E_Gadget_Site_Orient orient) +{ + Evas_Object *o; + Instance *inst = E_NEW(Instance, 1); + + if (!inst) return NULL; + inst->id = *id; + inst->orient = orient; + inst->o_bluez5 = o = elm_layout_add(parent); + _mod_icon_set(o, EINA_TRUE); + evas_object_size_hint_aspect_set(o, EVAS_ASPECT_CONTROL_BOTH, 1, 1); +// XXX: fill in later when we have gotten this far +// e_gadget_configure_cb_set(o, _gad_config); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _gad_mouse_down, inst); + if (*id != -1) + evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _gad_del, inst); + instances = eina_list_append(instances, inst); + return o; +} + +///////////////////////////////////////////////////////////////////////////// + +static void +_popup_del(Instance *inst) +{ + E_FREE_FUNC(inst->popup, e_object_del); +} + +static void +_popup_del_cb(void *obj) +{ + _popup_del(e_object_data_get(obj)); +} + +static void +_popup_comp_del_cb(void *data, Evas_Object *obj EINA_UNUSED) +{ + _popup_del(data); +} + +static void +_popup_new(Instance *inst) +{ + inst->popup = e_gadcon_popup_new(inst->gcc, 0); + + e_gadcon_popup_content_set(inst->popup, ebluez5_popup_content_add(e_comp->elm, inst)); + e_comp_object_util_autoclose(inst->popup->comp_object, _popup_comp_del_cb, NULL, inst); + e_gadcon_popup_show(inst->popup); + e_object_data_set(E_OBJECT(inst->popup), inst); + E_OBJECT_DEL_SET(inst->popup, _popup_del_cb); +} + +static void +_ebluez5_cb_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event) +{ + Instance *inst = data; + Evas_Event_Mouse_Down *ev = event; + + if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return; + if (ev->button != 1) return; + if (!inst->popup) _popup_new(inst); + else _popup_del(inst); +} + +static E_Gadcon_Client * +_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) +{ + Evas_Object *o; + Instance *inst = E_NEW(Instance, 1); + + if (!inst) return NULL; + inst->o_bluez5 = o = edje_object_add(gc->evas); + _mod_icon_set(o, EINA_FALSE); + inst->gcc = e_gadcon_client_new(gc, name, id, style, o); + inst->gcc->data = inst; + e_gadcon_client_util_menu_attach(inst->gcc); + evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _ebluez5_cb_mouse_down, inst); + instances = eina_list_append(instances, inst); + return inst->gcc; +} + +static void +_gc_shutdown(E_Gadcon_Client *gcc) +{ + Instance *inst = gcc->data;; + + if (!inst) return; + instances = eina_list_remove(instances, inst); + _popup_del(inst); + E_FREE_FUNC(inst->o_bluez5, evas_object_del); + E_FREE(inst); +} + +static const char * +_gc_id_new(const E_Gadcon_Client_Class *client_class EINA_UNUSED) +{ + static char tmpbuf[128]; + snprintf(tmpbuf, sizeof(tmpbuf), "bluez5.%d", eina_list_count(instances)); + return tmpbuf; +} + +static void +_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient EINA_UNUSED) +{ + e_gadcon_client_aspect_set(gcc, 16, 16); + e_gadcon_client_min_size_set(gcc, 16, 16); +} + +static const char * +_gc_label(const E_Gadcon_Client_Class *client_class EINA_UNUSED) +{ + return _("Bluez5"); +} + +static Evas_Object * +_gc_icon(const E_Gadcon_Client_Class *client_class EINA_UNUSED, Evas *evas) +{ + Evas_Object *o = NULL; + char buf[4096]; + + snprintf(buf, sizeof(buf), "%s/e-module-bluez5.edj", mod->dir); + o = edje_object_add(evas); + edje_object_file_set(o, buf, "icon"); + return o; +} + +static const E_Gadcon_Client_Class _gc_class = { + GADCON_CLIENT_CLASS_VERSION, "bluez5", + {_gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, NULL}, + E_GADCON_CLIENT_STYLE_PLAIN +}; + +///////////////////////////////////////////////////////////////////////////// +void +ebluez5_popups_show(void) +{ + Eina_List *l; + Instance *inst; + + EINA_LIST_FOREACH(instances, l, inst) + { + if (inst->gcc) + { + if (!inst->popup) _popup_new(inst); + } + else + { + if (!inst->pop) _gad_popup_do(inst); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +/* Module Functions */ +E_API void * +e_modapi_init(E_Module *m) +{ + mod = m; + + conf_edd = E_CONFIG_DD_NEW("Config", Config); +#undef T +#undef D +#define T Config +#define D conf_edd + E_CONFIG_VAL(D, T, lock_dev_addr, STR); + E_CONFIG_VAL(D, T, unlock_dev_addr, STR); + + ebluez5_config = e_config_domain_load("module.ebluez5", conf_edd); + if (!ebluez5_config) ebluez5_config = E_NEW(Config, 1); + + ebluze5_popup_init(); + bz_init(); + + e_gadcon_provider_register(&_gc_class); + e_gadget_type_add("Bluetooth", _gad_create, NULL); + + return m; +} + +E_API int +e_modapi_shutdown(E_Module *m EINA_UNUSED) +{ + E_CONFIG_DD_FREE(conf_edd); + + eina_stringshare_del(ebluez5_config->lock_dev_addr); + eina_stringshare_del(ebluez5_config->unlock_dev_addr); + free(ebluez5_config); + ebluez5_config = NULL; + + bz_shutdown(); + ebluze5_popup_shutdown(); + + e_gadget_type_del("Bluetooth"); + e_gadcon_provider_unregister(&_gc_class); + return 1; +} + +E_API int +e_modapi_save(E_Module *m EINA_UNUSED) +{ + e_config_domain_save("module.ebluez5", conf_edd, ebluez5_config); + return 1; +} diff --git a/src/modules/bluez5/e_mod_main.h b/src/modules/bluez5/e_mod_main.h new file mode 100644 index 000000000..269726638 --- /dev/null +++ b/src/modules/bluez5/e_mod_main.h @@ -0,0 +1,68 @@ +#ifndef E_MOD_MAIN_H +#define E_MOD_MAIN_H + +#include +#include "bz.h" + +typedef struct _Instance Instance; +struct _Instance +{ + // common info + Evas_Object *o_bluez5; + // e_gadcon info + E_Gadcon_Client *gcc; + E_Gadcon_Popup *popup; + // e_gadget info + Evas_Object *pop; + Evas_Object *popcontent; + int id; + E_Gadget_Site_Orient orient; +}; + +typedef struct _Config Config; +struct _Config +{ + const char *lock_dev_addr; + const char *unlock_dev_addr; +}; + +extern Config *ebluez5_config; + +E_API extern E_Module_Api e_modapi; + +E_API void *e_modapi_init(E_Module *m); +E_API int e_modapi_shutdown(E_Module *m); +E_API int e_modapi_save(E_Module *m); + +void ebluez5_popups_show(void); + +void ebluez5_popup_hide(Instance *inst); + +Evas_Object *ebluez5_popup_content_add(Evas_Object *base, Instance *inst); +void ebluze5_popup_init(void); +void ebluze5_popup_shutdown(void); +void ebluze5_popup_clear(void); +void ebluez5_popup_adapter_add(Obj *o); +void ebluez5_popup_adapter_del(Obj *o); +void ebluez5_popup_adapter_change(Obj *o); +void ebluez5_popup_device_add(Obj *o); +void ebluez5_popup_device_del(Obj *o); +void ebluez5_popup_device_change(Obj *o); + +void ebluez5_agent_agent_release(void); +void ebluez5_agent_agent_cancel(void); +void ebluez5_agent_agent_req_pin(Eldbus_Message *msg); +void ebluez5_agent_agent_disp_pin(Eldbus_Message *msg); +void ebluez5_agent_req_pass(Eldbus_Message *msg); +void ebluez5_agent_disp_pass(Eldbus_Message *msg); +void ebluez5_agent_req_confirm(Eldbus_Message *msg); +void ebluez5_agent_req_auth(Eldbus_Message *msg); +void ebluez5_agent_auth_service(Eldbus_Message *msg); + +Evas_Object *util_obj_icon_add(Evas_Object *base, Obj *o, int size); +Evas_Object *util_obj_icon_rssi_add(Evas_Object *base, Obj *o, int size); +Evas_Object *util_check_add(Evas_Object *base, const char *text, const char *tip, Eina_Bool state); +Evas_Object *util_button_icon_add(Evas_Object *base, const char *icon, const char *tip); +const char *util_obj_name_get(Obj *o); + +#endif diff --git a/src/modules/bluez5/e_mod_popup.c b/src/modules/bluez5/e_mod_popup.c new file mode 100644 index 000000000..8152802f0 --- /dev/null +++ b/src/modules/bluez5/e_mod_popup.c @@ -0,0 +1,714 @@ +#include "e_mod_main.h" + +static Elm_Genlist_Item_Class *adapt_itc = NULL; +static Elm_Genlist_Item_Class *dev_itc = NULL; +static Elm_Genlist_Item_Class *group_itc = NULL; +static Eina_List *lists = NULL; +static Eina_List *adapters = NULL; +static Eina_List *devices = NULL; + +static void +_adapter_add(Evas_Object *gl, Obj *o) +{ + Elm_Object_Item *it = evas_object_data_get(gl, "adapters_item");; + elm_genlist_item_append(gl, adapt_itc, o, it, ELM_GENLIST_ITEM_NONE, + NULL, NULL); +} + +static int +_cb_insert_cmp(const void *ai, const void *bi) +{ + Obj *a = elm_object_item_data_get(ai); + Obj *b = elm_object_item_data_get(bi); + Eina_Bool apub = EINA_FALSE, bpub = EINA_FALSE; + + if ((!a) || (!a->address)) return -1; + if ((!b) || (!b->address)) return 1; + + // prefer paired at top + if ((a->paired) && (!b->paired)) return -1; + if ((!a->paired) && (b->paired)) return 1; + // prefer public addresses next after being paired + if ((a->address_type) && (!strcmp(a->address_type, "public"))) + apub = EINA_TRUE; + if ((b->address_type) && (!strcmp(b->address_type, "public"))) + bpub = EINA_TRUE; + if ((apub) && (!bpub)) return -1; + if ((!apub) && (bpub)) return 1; + // and sort by address + return strcmp(a->address, b->address); +} + +static void +_device_add(Evas_Object *gl, Obj *o) +{ + Elm_Object_Item *it = evas_object_data_get(gl, "devices_item");; + + elm_genlist_item_sorted_insert(gl, dev_itc, o, it, ELM_GENLIST_ITEM_NONE, + _cb_insert_cmp, NULL, NULL); +} + +static void +_cb_power(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + if (elm_check_state_get(obj)) bz_obj_power_on(o); + else bz_obj_power_off(o); +} + +static void +_cb_scan(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + if (elm_check_state_get(obj)) bz_obj_discover_start(o); + else bz_obj_discover_stop(o); +} + +static void +_cb_visible(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + if (elm_check_state_get(obj)) bz_obj_discoverable(o); + else bz_obj_undiscoverable(o); +} + +static void +_cb_pairable(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + if (elm_check_state_get(obj)) bz_obj_pairable(o); + else bz_obj_unpairable(o); +} + +static void +_cb_connect(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_connect(o); +} + +static void +_cb_disconnect(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_disconnect(o); +} + +static void +_cb_trust(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_trust(o); +} + +static void +_cb_distrust(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_distrust(o); +} + +static void +_cb_pair(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_pair(o); +} + +static void +_cb_unpair(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + bz_obj_remove(o); +} + +static void +_agent_done(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + Elm_Object_Item *it; + + if (o->agent_request) + { + eina_stringshare_del(o->agent_request); + o->agent_request = NULL; + } + EINA_LIST_FOREACH(lists, l, gl) + { + for (it = elm_genlist_first_item_get(gl); it; + it = elm_genlist_item_next_get(it)) + { + if (o == elm_object_item_data_get(it)) + { + elm_genlist_item_update(it); + break; + } + } + } +} + +static void +_cb_agent_ok(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + Obj *o = data; + + if ((o->agent_entry_fn) && (o->agent_msg_ok)) + { + Evas_Object *en = evas_object_data_get(obj, "entry"); + + if (en) + { + const char *s = elm_object_text_get(en); + + if (s) o->agent_entry_fn(o->agent_msg_ok, s); + } + } + if (o->agent_msg_err) + { + bz_agent_msg_drop(o->agent_msg_err); + o->agent_msg_err = NULL; + } + if (o->agent_msg_ok) + { + bz_agent_msg_reply(o->agent_msg_ok); + o->agent_msg_ok = NULL; + } + _agent_done(o); +} + +static void +_cb_agent_cancel(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Obj *o = data; + if (o->agent_msg_ok) + { + bz_agent_msg_drop(o->agent_msg_ok); + o->agent_msg_ok = NULL; + } + if (o->agent_msg_err) + { + bz_agent_msg_reply(o->agent_msg_err); + o->agent_msg_err = NULL; + } + _agent_done(o); +} + +static char * +_cb_group_text_get(void *data, Evas_Object *obj EINA_UNUSED, + const char *part EINA_UNUSED) +{ + if (!data) return strdup(_("Adapters")); + return strdup(_("Devices")); +} + +static Evas_Object * +_cb_group_content_get(void *data EINA_UNUSED, Evas_Object *obj, + const char *part EINA_UNUSED) +{ + if (!strcmp(part, "elm.swallow.icon")) + { + Evas_Object *ic = elm_icon_add(obj); + if (!data) + elm_icon_standard_set(ic, "computer"); + else + elm_icon_standard_set(ic, "system-run"); + evas_object_size_hint_min_set(ic, + ELM_SCALE_SIZE(16), + ELM_SCALE_SIZE(16)); + return ic; + } + return NULL; +} + +static char * +_cb_adapt_text_get(void *data, Evas_Object *obj EINA_UNUSED, + const char *part EINA_UNUSED) +{ + Obj *o = data; + + if (!strcmp(part, "elm.text")) + { + return strdup(util_obj_name_get(o)); + } + else if (!strcmp(part, "elm.text.sub")) + { + if (o->address) return strdup(o->address); + return strdup(_("Unknown Address")); + } + return NULL; +} + +static Evas_Object * +_cb_adapt_content_get(void *data EINA_UNUSED, Evas_Object *obj, + const char *part EINA_UNUSED) +{ + Obj *o = data; + + if (!strcmp(part, "elm.swallow.icon")) + { + return util_obj_icon_add(obj, o, 48); + } + else if (!strcmp(part, "elm.swallow.end")) + { + Evas_Object *tab, *ck; + + tab = elm_table_add(obj); + evas_object_size_hint_weight_set(tab, EVAS_HINT_EXPAND, 0); + evas_object_size_hint_align_set(tab, EVAS_HINT_FILL, 0.0); + + ck = util_check_add(obj, _("Power"), _("Enable power for this adapter"), + o->powered); + evas_object_smart_callback_add(ck, "changed", _cb_power, o); + elm_table_pack(tab, ck, 0, 0, 1, 1); + evas_object_show(ck); + + ck = util_check_add(obj, _("Visible"), _("Make this adapter visible to other devices"), + o->discoverable); + evas_object_smart_callback_add(ck, "changed", _cb_visible, o); + elm_table_pack(tab, ck, 1, 0, 1, 1); + evas_object_show(ck); + + ck = util_check_add(obj, _("Scan"), _("Scan for other devices"), + o->discovering); + evas_object_smart_callback_add(ck, "changed", _cb_scan, o); + elm_table_pack(tab, ck, 0, 1, 1, 1); + evas_object_show(ck); + + ck = util_check_add(obj, _("Pairable"), _("Allow this adapter to have other devices request to pair with it"), + o->pairable); + evas_object_smart_callback_add(ck, "changed", _cb_pairable, o); + elm_table_pack(tab, ck, 1, 1, 1, 1); + evas_object_show(ck); + + return tab; + } + return NULL; +} + +static char * +_cb_dev_text_get(void *data, Evas_Object *obj EINA_UNUSED, + const char *part EINA_UNUSED) +{ + Obj *o = data; + return strdup(util_obj_name_get(o)); +} + +static Evas_Object * +_cb_dev_content_get(void *data EINA_UNUSED, Evas_Object *obj, + const char *part EINA_UNUSED) +{ + Obj *o = data; + char buf[512]; + + if (!strcmp(part, "elm.swallow.icon")) + { + Evas_Object *ic, *bx; + + bx = elm_box_add(obj); + ic = util_obj_icon_add(obj, o, 24); + snprintf(buf, sizeof(buf), + _("Address: %s (%s)
" + "Services: %s%s%s%s%s%s%s%s%s
" + "Trusted: %s
" + "Blocked: %s
" + ) + , + (o->address) ? o->address : _("Unknown"), + (o->address_type) ? o->address_type : _("Unknown"), + (o->klass & BZ_OBJ_CLASS_SERVICE_LIMITED_DISCOVERABLE) ? _("Limited-Discoverable ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_POSITIONING_BIT) ? _("Positioning ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_NETWORKING_BIT) ? _("Networking ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_RENDERING_BIT) ? _("Rendering ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_CAPTURING_BIT) ? _("Capture ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_OBJECT_TRANSFER_BIT) ? _("OBEX ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_AUDIO_BIT) ? _("Audio ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_TELEPHONY_BIT) ? _("Telephony ") : "", + (o->klass & BZ_OBJ_CLASS_SERVICE_INFORMATION_BIT) ? _("Information ") : "", + (o->trusted) ? _("Yes") : _("No"), + (o->blocked) ? _("Yes") : _("No") + ); + elm_object_tooltip_text_set(ic, buf); + elm_box_pack_end(bx, ic); + evas_object_show(ic); + return bx; + } + else if (!strcmp(part, "elm.swallow.end")) + { + Evas_Object *bx, *ic, *bt, *lb, *tb, *en, *rec; + + bx = elm_box_add(obj); + elm_box_horizontal_set(bx, EINA_TRUE); + if (o->paired) + { + if (o->connected) + { + bt = util_button_icon_add(obj, "network-offline", + _("Disconnect this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_disconnect, o); + } + else + { + bt = util_button_icon_add(obj, "network-transmit-receive", + _("Connect this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_connect, o); + } + elm_box_pack_end(bx, bt); + evas_object_show(bt); + + if (o->trusted) + { + bt = util_button_icon_add(obj, "security-low", + _("Disrust this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_distrust, o); + } + else + { + bt = util_button_icon_add(obj, "security-high", + _("Trust this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_trust, o); + } + elm_box_pack_end(bx, bt); + evas_object_show(bt); + } + if (!o->paired) + { + if (o->agent_request) + { + if (o->agent_entry_fn) + { + tb = elm_table_add(obj); + + rec = evas_object_rectangle_add(evas_object_evas_get(obj)); + evas_object_size_hint_min_set(rec, ELM_SCALE_SIZE(80), ELM_SCALE_SIZE(1)); + elm_table_pack(tb, rec, 0, 0, 1, 1); + + en = elm_entry_add(obj); + elm_entry_single_line_set(en, EINA_TRUE); + elm_entry_scrollable_set(en, EINA_TRUE); + elm_scroller_policy_set(en, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_OFF); + elm_object_part_text_set(en, "guide", o->agent_request); +// elm_entry_password_set(en, EINA_TRUE); + evas_object_smart_callback_add(en, "activated", _cb_agent_ok, o); + evas_object_smart_callback_add(en, "aborted", _cb_agent_cancel, o); + elm_table_pack(tb, en, 0, 0, 1, 1); + evas_object_show(en); + + elm_box_pack_end(bx, tb); + evas_object_show(tb); + + bt = util_button_icon_add(obj, "list-add", + _("Pair with this device")); + evas_object_data_set(bt, "entry", en); + evas_object_smart_callback_add(bt, "clicked", _cb_agent_ok, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + + bt = util_button_icon_add(obj, "list-remove", + _("Reject pairing")); + evas_object_smart_callback_add(bt, "clicked", _cb_agent_cancel, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + } + else + { + lb = elm_label_add(obj); + elm_layout_text_set(lb, NULL, o->agent_request); + elm_box_pack_end(bx, lb); + evas_object_show(lb); + + bt = util_button_icon_add(obj, "list-add", + _("Pair with this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_agent_ok, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + + bt = util_button_icon_add(obj, "list-remove", + _("Reject pairing")); + evas_object_smart_callback_add(bt, "clicked", _cb_agent_cancel, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + } + } + else + { + bt = util_button_icon_add(obj, "list-add", + _("Pair with this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_pair, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + } + } + else + { + bt = util_button_icon_add(obj, "list-remove", + _("Unpair with this device")); + evas_object_smart_callback_add(bt, "clicked", _cb_unpair, o); + elm_box_pack_end(bx, bt); + evas_object_show(bt); + } + + ic = util_obj_icon_rssi_add(obj, o, 24); + elm_box_pack_end(bx, ic); + evas_object_show(ic); + return bx; + } + return NULL; +} + +static void +_cb_list_del(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + lists = eina_list_remove(lists, obj); +} + +/* +static void +_cb_settings(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Instance *inst = data; + ebluez5_popup_hide(inst); +} +*/ + +Evas_Object * +ebluez5_popup_content_add(Evas_Object *base, Instance *inst) +{ + Evas_Object *o, *box, *tab, *gl; + Eina_List *l; + Elm_Object_Item *it; + Obj *oo; + + o = box = elm_box_add(base); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + + tab = o = elm_table_add(base); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + + o = evas_object_rectangle_add(evas_object_evas_get(base)); + evas_object_size_hint_min_set(o, ELM_SCALE_SIZE(320), ELM_SCALE_SIZE(240)); + evas_object_size_hint_max_set(o, ELM_SCALE_SIZE(560), ELM_SCALE_SIZE(400)); + elm_table_pack(tab, o, 0, 0, 1, 1); + + o = gl = elm_genlist_add(base); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_genlist_mode_set(o, ELM_LIST_LIMIT); + elm_genlist_select_mode_set(o, ELM_OBJECT_SELECT_MODE_NONE); + + lists = eina_list_append(lists, gl); + evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _cb_list_del, inst); + + it = elm_genlist_item_append(gl, group_itc, NULL, NULL, + ELM_GENLIST_ITEM_GROUP, NULL, NULL); + evas_object_data_set(gl, "adapters_item", it); + it = elm_genlist_item_append(gl, group_itc, gl, NULL, + ELM_GENLIST_ITEM_GROUP, NULL, NULL); + evas_object_data_set(gl, "devices_item", it); + + EINA_LIST_FOREACH(adapters, l, oo) + { + _adapter_add(gl, oo); + } + EINA_LIST_FOREACH(devices, l, oo) + { + _device_add(gl, oo); + } + + elm_table_pack(tab, o, 0, 0, 1, 1); + evas_object_show(o); + + elm_box_pack_end(box, tab); + evas_object_show(tab); +/* + o = elm_separator_add(base); + elm_separator_horizontal_set(o, EINA_TRUE); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, o); + evas_object_show(o); + + o = elm_button_add(base); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_layout_text_set(o, NULL, _("Settings")); + elm_object_tooltip_text_set(o, _("Bring up more detailed Bluetooth settings")); + evas_object_smart_callback_add(o, "clicked", _cb_settings, inst); + elm_box_pack_end(box, o); + evas_object_show(o); +*/ + return box; +} + +void +ebluze5_popup_init(void) +{ + adapt_itc = elm_genlist_item_class_new(); + adapt_itc->item_style = "double_label"; + adapt_itc->func.text_get = _cb_adapt_text_get; + adapt_itc->func.content_get = _cb_adapt_content_get; + adapt_itc->func.state_get = NULL; + adapt_itc->func.del = NULL; + + dev_itc = elm_genlist_item_class_new(); + dev_itc->item_style = "default"; + dev_itc->func.text_get = _cb_dev_text_get; + dev_itc->func.content_get = _cb_dev_content_get; + dev_itc->func.state_get = NULL; + dev_itc->func.del = NULL; + + group_itc = elm_genlist_item_class_new(); + group_itc->item_style = "group_index"; + group_itc->func.text_get = _cb_group_text_get; + group_itc->func.content_get = _cb_group_content_get; + group_itc->func.state_get = NULL; + group_itc->func.del = NULL; +} + +void +ebluze5_popup_shutdown(void) +{ + ebluze5_popup_clear(); + elm_genlist_item_class_free(group_itc); + elm_genlist_item_class_free(dev_itc); + elm_genlist_item_class_free(adapt_itc); + group_itc = NULL; + dev_itc = NULL; + adapt_itc = NULL; +} + +void +ebluze5_popup_clear(void) +{ + Eina_List *l; + Evas_Object *gl; + + adapters = eina_list_free(adapters); + devices = eina_list_free(devices); + EINA_LIST_FOREACH(lists, l, gl) + { + elm_genlist_clear(gl); + } +} + +void +ebluez5_popup_adapter_add(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + + adapters = eina_list_append(adapters, o); + EINA_LIST_FOREACH(lists, l, gl) + { + _adapter_add(gl, o); + } +} + +void +ebluez5_popup_adapter_del(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + Elm_Object_Item *it; + + EINA_LIST_FOREACH(lists, l, gl) + { + for (it = elm_genlist_first_item_get(gl); it; + it = elm_genlist_item_next_get(it)) + { + if (o == elm_object_item_data_get(it)) + { + elm_object_item_del(it); + break; + } + } + } + adapters = eina_list_remove(adapters, o); +} + +void +ebluez5_popup_adapter_change(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + Elm_Object_Item *it; + + EINA_LIST_FOREACH(lists, l, gl) + { + for (it = elm_genlist_first_item_get(gl); it; + it = elm_genlist_item_next_get(it)) + { + if (o == elm_object_item_data_get(it)) + { + elm_genlist_item_update(it); + break; + } + } + } +} + +void +ebluez5_popup_device_add(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + + devices = eina_list_append(devices, o); + EINA_LIST_FOREACH(lists, l, gl) + { + _device_add(gl, o); + } +} + +void +ebluez5_popup_device_del(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + Elm_Object_Item *it; + + EINA_LIST_FOREACH(lists, l, gl) + { + for (it = elm_genlist_first_item_get(gl); it; + it = elm_genlist_item_next_get(it)) + { + if (o == elm_object_item_data_get(it)) + { + elm_object_item_del(it); + break; + } + } + } + devices = eina_list_remove(devices, o); +} + +void +ebluez5_popup_device_change(Obj *o) +{ + Eina_List *l; + Evas_Object *gl; + Elm_Object_Item *it; + Eina_Bool alert = EINA_FALSE; + + if (o->agent_alert) + { + alert = EINA_TRUE; + o->agent_alert = EINA_FALSE; + if (!lists) ebluez5_popups_show(); + } + EINA_LIST_FOREACH(lists, l, gl) + { + for (it = elm_genlist_first_item_get(gl); it; + it = elm_genlist_item_next_get(it)) + { + if (o == elm_object_item_data_get(it)) + { + elm_genlist_item_update(it); + if (alert) + elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE); + break; + } + } + } +} diff --git a/src/modules/bluez5/e_mod_util.c b/src/modules/bluez5/e_mod_util.c new file mode 100644 index 000000000..aedd37d6a --- /dev/null +++ b/src/modules/bluez5/e_mod_util.c @@ -0,0 +1,262 @@ +#include "e_mod_main.h" + +Evas_Object * +util_obj_icon_add(Evas_Object *base, Obj *o, int size) +{ + Evas_Object *ic = elm_icon_add(base); + unsigned int maj, min; + const char *s ="bluetooth-active"; + + // XXX: replace this with a much better database... + maj = o->klass & BZ_OBJ_CLASS_MAJ_MASK; + if (maj == BZ_OBJ_CLASS_MAJ_MISC) + { + s ="bluetooth-active"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_COMPUTER) + { + min = o->klass & BZ_OBJ_CLASS_MIN_COMPUTER_MASK; + if (min == BZ_OBJ_CLASS_MIN_COMPUTER_DESKTOP) + s = "computer"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_SERVER) + s = "computer"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_LAPTOP) + s = "computer-laptop"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_CLAMSHELL) + s = "computer-laptop"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_PDA) + s = "pda"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_WEARABLE) + s = "pda"; + else if (min == BZ_OBJ_CLASS_MIN_COMPUTER_TABLET) + s = "pda"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_PHONE) + { + min = o->klass & BZ_OBJ_CLASS_MIN_PHONE_MASK; + if (min == BZ_OBJ_CLASS_MIN_PHONE_CELL) + s = "phone"; + else if (min == BZ_OBJ_CLASS_MIN_PHONE_CORDLESS) + s = "phone"; + else if (min == BZ_OBJ_CLASS_MIN_PHONE_SMARTPHONE) + s = "phone"; + else if (min == BZ_OBJ_CLASS_MIN_PHONE_WIRED) + s = "modem"; + else if (min == BZ_OBJ_CLASS_MIN_PHONE_ISDN) + s = "modem"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_LAN) + { + s = "network-wired"; + // XXX: handle (top is max availability) + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_7 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_6 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_5 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_4 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_3 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_2 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_1 + // BZ_OBJ_CLASS_MIN_LAN_AVAIL_0 + } + else if (maj == BZ_OBJ_CLASS_MAJ_AV) + { + min = o->klass & BZ_OBJ_CLASS_MIN_AV_MASK; + if (min == BZ_OBJ_CLASS_MIN_AV_WEARABLE_HEADSET) + s = "audio-input-microphone"; + else if (min == BZ_OBJ_CLASS_MIN_AV_HANDS_FREE) + s = "audio-input-microphone"; + else if (min == BZ_OBJ_CLASS_MIN_AV_MIC) + s = "audio-input-microphone"; + else if (min == BZ_OBJ_CLASS_MIN_AV_SPEAKER) + s = "audio-volume-high"; + else if (min == BZ_OBJ_CLASS_MIN_AV_HEADPHONES) + s = "audio-volume-high"; + else if (min == BZ_OBJ_CLASS_MIN_AV_PORTABLE_AUDIO) + s = "audio-volume-high"; + else if (min == BZ_OBJ_CLASS_MIN_AV_CAR_AUDIO) + s = "audio-volume-high"; + else if (min == BZ_OBJ_CLASS_MIN_AV_SET_TOP_BOX) + s = "modem"; + else if (min == BZ_OBJ_CLASS_MIN_AV_HIFI_AUDIO) + s = "audio-volume-high"; + else if (min == BZ_OBJ_CLASS_MIN_AV_VCR) + s = "media-tape"; + else if (min == BZ_OBJ_CLASS_MIN_AV_VIDEO_CAMERA) + s = "camera-photo"; + else if (min == BZ_OBJ_CLASS_MIN_AV_CAMCORDER) + s = "camera-photo"; + else if (min == BZ_OBJ_CLASS_MIN_AV_VIDEO_MONITOR) + s = "video-display"; + else if (min == BZ_OBJ_CLASS_MIN_AV_VIDEO_DISPLAY_SPEAKER) + s = "video-display"; + else if (min == BZ_OBJ_CLASS_MIN_AV_VIDEO_CONFERENCE) + s = "video-display"; + else if (min == BZ_OBJ_CLASS_MIN_AV_GAMING) + s = "input-gaming"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_PERIPHERAL) + { + s = "input-keyboard"; + + // XXX: handle bits + ide below + if (o->klass & BZ_OBJ_CLASS_MIN_PERIPHERAL_KEYBOARD_BIT) + s = "input-keyboard"; + else if (o->klass & BZ_OBJ_CLASS_MIN_PERIPHERAL_MOUSE_BIT) + s = "input-mouse"; + + min = o->klass & BZ_OBJ_CLASS_MIN_PERIPHERAL_MASK2; + if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_JOYSTICK) + s = "input-gaming"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_GAMEPAD) + s = "input-gaming"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_REMOTE) + s = "input-gaming"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_SENSING) + s = "input-gaming"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_DIGITIZER_TAB) + s = "input-tablet"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_CARD_READER) + s = "media-flash"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_PEN) + s = "input-mouse"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_SCANNER) + s = "scanner"; + else if (min == BZ_OBJ_CLASS_MIN_PERIPHERAL_WAND) + s = "input-mouse"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_IMAGING) + { + // XXX: handle permutations of bits + if (o->klass & BZ_OBJ_CLASS_MIN_IMAGING_CAMERA_BIT) + s = "camera-photo"; + else if (o->klass & BZ_OBJ_CLASS_MIN_IMAGING_SCANNER_BIT) + s = "scanner"; + else if (o->klass & BZ_OBJ_CLASS_MIN_IMAGING_PRINTER_BIT) + s = "printer"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_WEARABLE) + { + min = o->klass & BZ_OBJ_CLASS_MIN_WEARABLE_MASK; + if (min == BZ_OBJ_CLASS_MIN_WEARABLE_WATCH) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_WEARABLE_PAGER) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_WEARABLE_JACKET) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_WEARABLE_HELMET) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_WEARABLE_GLASSES) + s = "cpu"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_TOY) + { + min = o->klass & BZ_OBJ_CLASS_MIN_TOY_MASK; + if (min == BZ_OBJ_CLASS_MIN_TOY_ROBOT) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_TOY_VEHICLE) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_TOY_DOLL) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_TOY_CONTROLLER) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_TOY_GAME) + s = "cpu"; + } + else if (maj == BZ_OBJ_CLASS_MAJ_HEALTH) + { + min = o->klass & BZ_OBJ_CLASS_MIN_HEALTH_MASK; + if (min == BZ_OBJ_CLASS_MIN_HEALTH_BLOOD_PRESSURE) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_THERMOMETER) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_SCALES) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_GLUCOSE) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_PULSE_OXIMITER) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_HEART_RATE) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_HEALTH_DATA_DISP) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_STEP) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_BODY_COMPOSITION) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_PEAK_FLOW) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_MEDICATION) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_KNEE_PROSTHESIS) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_ANKLE_PROSTHESIS) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_GENERIC_HEALTH) + s = "cpu"; + else if (min == BZ_OBJ_CLASS_MIN_HEALTH_PRESONAL_MOBILITY) + s = "cpu"; + } + elm_icon_standard_set(ic, s); + evas_object_size_hint_min_set(ic, + ELM_SCALE_SIZE(size), + ELM_SCALE_SIZE(size)); + return ic; +} + +Evas_Object * +util_obj_icon_rssi_add(Evas_Object *base, Obj *o, int size) +{ + Evas_Object *ic = elm_icon_add(base); + char buf[64]; + if (o->rssi <= -80) + elm_icon_standard_set(ic, "network-cellular-signal-excellent"); + else if (o->rssi <= -72) + elm_icon_standard_set(ic, "network-cellular-signal-good"); + else if (o->rssi <= -64) + elm_icon_standard_set(ic, "network-cellular-signal-ok"); + else if (o->rssi <= -56) + elm_icon_standard_set(ic, "network-cellular-signal-weak"); + else if (o->rssi <= -48) + elm_icon_standard_set(ic, "network-cellular-signal-none"); + else + elm_icon_standard_set(ic, "network-cellular-signal-acquiring"); + snprintf(buf, sizeof(buf), "RSSI: %i", (int)o->rssi); + elm_object_tooltip_text_set(ic, buf); + evas_object_size_hint_min_set(ic, + ELM_SCALE_SIZE(size), + ELM_SCALE_SIZE(size)); + return ic; +} + +Evas_Object * +util_check_add(Evas_Object *base, const char *text, const char *tip, Eina_Bool state) +{ + Evas_Object *ck = elm_check_add(base); + evas_object_size_hint_align_set(ck, 0.0, EVAS_HINT_FILL); + elm_layout_text_set(ck, NULL, text); + elm_object_tooltip_text_set(ck, tip); + elm_check_state_set(ck, state); + return ck; +} + +Evas_Object * +util_button_icon_add(Evas_Object *base, const char *icon, const char *tip) +{ + Evas_Object *ic, *bt = elm_button_add(base); + ic = elm_icon_add(base); + evas_object_size_hint_align_set(bt, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_icon_standard_set(ic, icon); + elm_object_tooltip_text_set(bt, tip); + elm_object_part_content_set(bt, NULL, ic); + evas_object_show(ic); + return bt; +} + +const char * +util_obj_name_get(Obj *o) +{ + if (o->name) return o->name; + if (o->alias) return o->alias; + if (o->address) return o->address; + return _("Uknown"); +} diff --git a/src/modules/bluez5/meson.build b/src/modules/bluez5/meson.build new file mode 100644 index 000000000..f6cffcc37 --- /dev/null +++ b/src/modules/bluez5/meson.build @@ -0,0 +1,9 @@ +src = files( + 'e_mod_main.c', + 'e_mod_popup.c', + 'e_mod_agent.c', + 'e_mod_util.c', + 'bz.c', + 'bz_obj.c', + 'bz_agent.c' +) diff --git a/src/modules/bluez5/module.desktop b/src/modules/bluez5/module.desktop new file mode 100644 index 000000000..fd102a832 --- /dev/null +++ b/src/modules/bluez5/module.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Link +Name=Bluez5 +Name[ca]=Bluez5 +Name[de]=Bluez5 +Name[fi]=Bluez5 +Name[gl]=Bluez5 +Name[ja]=Bluez5 +Name[ms]=Bluez5 +Name[sr]=Блуез5 +Name[tr]=Bluetooth +Icon=e-module-bluez5 +X-Enlightenment-ModuleType=utils diff --git a/src/modules/meson.build b/src/modules/meson.build index 11b93df3f..4150367e7 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -34,6 +34,7 @@ mods = [ 'geolocation', 'connman', 'bluez4', + 'bluez5', 'syscon', 'systray', 'appmenu',