#include "e_mod_main.h" #define CONNMAN_BUS_NAME "net.connman" #define CONNMAN_MANAGER_IFACE CONNMAN_BUS_NAME ".Manager" #define CONNMAN_SERVICE_IFACE CONNMAN_BUS_NAME ".Service" #define CONNMAN_TECHNOLOGY_IFACE CONNMAN_BUS_NAME ".Technology" #define MILLI_PER_SEC 1000 #define CONNMAN_CONNECTION_TIMEOUT 60 * MILLI_PER_SEC static unsigned int init_count; static Eldbus_Connection *conn; static struct Connman_Manager *connman_manager; static E_Connman_Agent *agent; EAPI int E_CONNMAN_EVENT_MANAGER_IN; EAPI int E_CONNMAN_EVENT_MANAGER_OUT; /* utility functions */ static void _eina_str_array_clean(Eina_Array *array) { const char *item; Eina_Array_Iterator itr; unsigned int i; EINA_ARRAY_ITER_NEXT(array, i, item, itr) eina_stringshare_del(item); eina_array_clean(array); } static void _dbus_str_array_to_eina(Eldbus_Message_Iter *value, Eina_Array **old, unsigned nelem) { Eldbus_Message_Iter *itr_array; Eina_Array *array; const char *s; EINA_SAFETY_ON_NULL_RETURN(value); EINA_SAFETY_ON_NULL_RETURN(old); EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value, "as", &itr_array)); array = *old; if (array == NULL) { array = eina_array_new(nelem); *old = array; } else _eina_str_array_clean(array); while (eldbus_message_iter_get_and_next(itr_array, 's', &s)) { eina_array_push(array, eina_stringshare_add(s)); DBG("Push %s", s); } return; } static enum Connman_State str_to_state(const char *s) { if (!strcmp(s, "offline")) return CONNMAN_STATE_OFFLINE; if (!strcmp(s, "idle")) return CONNMAN_STATE_IDLE; if (!strcmp(s, "association")) return CONNMAN_STATE_ASSOCIATION; if (!strcmp(s, "configuration")) return CONNMAN_STATE_CONFIGURATION; if (!strcmp(s, "ready")) return CONNMAN_STATE_READY; if (!strcmp(s, "online")) return CONNMAN_STATE_ONLINE; if (!strcmp(s, "disconnect")) return CONNMAN_STATE_DISCONNECT; if (!strcmp(s, "failure")) return CONNMAN_STATE_FAILURE; ERR("Unknown state %s", s); return CONNMAN_STATE_NONE; } const char *econnman_state_to_str(enum Connman_State state) { switch (state) { case CONNMAN_STATE_OFFLINE: return "offline"; case CONNMAN_STATE_IDLE: return "idle"; case CONNMAN_STATE_ASSOCIATION: return "association"; case CONNMAN_STATE_CONFIGURATION: return "configuration"; case CONNMAN_STATE_READY: return "ready"; case CONNMAN_STATE_ONLINE: return "online"; case CONNMAN_STATE_DISCONNECT: return "disconnect"; case CONNMAN_STATE_FAILURE: return "failure"; case CONNMAN_STATE_NONE: break; } return NULL; } static enum Connman_Service_Type str_to_type(const char *s) { if (!strcmp(s, "ethernet")) return CONNMAN_SERVICE_TYPE_ETHERNET; else if (!strcmp(s, "wifi")) return CONNMAN_SERVICE_TYPE_WIFI; else if (!strcmp(s, "bluetooth")) return CONNMAN_SERVICE_TYPE_BLUETOOTH; else if (!strcmp(s, "cellular")) return CONNMAN_SERVICE_TYPE_CELLULAR; DBG("Unknown type %s", s); return CONNMAN_SERVICE_TYPE_NONE; } const char *econnman_service_type_to_str(enum Connman_Service_Type type) { switch (type) { case CONNMAN_SERVICE_TYPE_ETHERNET: return "ethernet"; case CONNMAN_SERVICE_TYPE_WIFI: return "wifi"; case CONNMAN_SERVICE_TYPE_BLUETOOTH: return "bluetooth"; case CONNMAN_SERVICE_TYPE_CELLULAR: return "cellular"; case CONNMAN_SERVICE_TYPE_NONE: break; } return "other"; } /* ---- */ static void _service_parse_prop_changed(struct Connman_Service *cs, const char *prop_name, Eldbus_Message_Iter *value) { DBG("service %p %s prop %s", cs, cs->path, prop_name); if (strcmp(prop_name, "State") == 0) { const char *state; EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value, "s", &state)); cs->state = str_to_state(state); DBG("New state: %s %d", state, cs->state); } else if (strcmp(prop_name, "Name") == 0) { const char *name; EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value, "s", &name)); free(cs->name); cs->name = strdup(name); DBG("New name: %s", cs->name); } else if (strcmp(prop_name, "Type") == 0) { const char *type; EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value, "s", &type)); cs->type = str_to_type(type); DBG("New type: %s %d", type, cs->type); } else if (strcmp(prop_name, "Strength") == 0) { uint8_t strength; EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value, "y", &strength)); cs->strength = strength; DBG("New strength: %d", strength); } else if (strcmp(prop_name, "Security") == 0) { DBG("Old security count: %d", cs->security ? eina_array_count(cs->security) : 0); _dbus_str_array_to_eina(value, &cs->security, 2); DBG("New security count: %d", eina_array_count(cs->security)); } } static void _service_prop_dict_changed(struct Connman_Service *cs, Eldbus_Message_Iter *props) { Eldbus_Message_Iter *dict; while (eldbus_message_iter_get_and_next(props, 'e', &dict)) { char *name; Eldbus_Message_Iter *var; if (!eldbus_message_iter_arguments_get(dict, "sv", &name, &var)) continue; _service_parse_prop_changed(cs, name, var); } } static void _service_prop_changed(void *data, const Eldbus_Message *msg) { struct Connman_Service *cs = data; Eldbus_Message_Iter *var; const char *name; if (!eldbus_message_arguments_get(msg, "sv", &name, &var)) return; _service_parse_prop_changed(cs, name, var); } struct connection_data { struct Connman_Service *cs; Econnman_Simple_Cb cb; void *user_data; }; static void _service_free(struct Connman_Service *cs) { Eldbus_Object *obj; if (!cs) return; if (cs->pending.connect) { eldbus_pending_cancel(cs->pending.connect); free(cs->pending.data); } if (cs->pending.disconnect) { eldbus_pending_cancel(cs->pending.disconnect); free(cs->pending.data); } if (cs->pending.remov) { eldbus_pending_cancel(cs->pending.remov); free(cs->pending.data); } free(cs->name); _eina_str_array_clean(cs->security); eina_array_free(cs->security); eina_stringshare_del(cs->path); obj = eldbus_proxy_object_get(cs->service_iface); eldbus_proxy_unref(cs->service_iface); eldbus_object_unref(obj); free(cs); } static struct Connman_Service *_service_new(const char *path, Eldbus_Message_Iter *props) { struct Connman_Service *cs; Eldbus_Object *obj; EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL); cs = calloc(1, sizeof(*cs)); EINA_SAFETY_ON_NULL_RETURN_VAL(cs, NULL); cs->path = eina_stringshare_add(path); obj = eldbus_object_get(conn, CONNMAN_BUS_NAME, path); cs->service_iface = eldbus_proxy_get(obj, CONNMAN_SERVICE_IFACE); eldbus_proxy_signal_handler_add(cs->service_iface, "PropertyChanged", _service_prop_changed, cs); _service_prop_dict_changed(cs, props); return cs; } static void _service_connection_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { struct connection_data *cd = data; if (cd->cb) { const char *s = NULL; eldbus_message_error_get(msg, NULL, &s); cd->cb(cd->user_data, s); } cd->cs->pending.connect = NULL; cd->cs->pending.disconnect = NULL; cd->cs->pending.remov = NULL; cd->cs->pending.data = NULL; free(cd); } bool econnman_service_connect(struct Connman_Service *cs, Econnman_Simple_Cb cb, void *data) { struct connection_data *cd; EINA_SAFETY_ON_NULL_RETURN_VAL(cs, false); if (cs->pending.connect || cs->pending.disconnect || cs->pending.remov) { ERR("Pending connection: connect=%p disconnect=%p remov=%p", cs->pending.connect, cs->pending.disconnect, cs->pending.remov); return false; } cd = calloc(1, sizeof(*cd)); EINA_SAFETY_ON_NULL_GOTO(cd, fail); cd->cs = cs; cd->cb = cb; cd->user_data = data; cs->pending.connect = eldbus_proxy_call(cs->service_iface, "Connect", _service_connection_cb, cd, CONNMAN_CONNECTION_TIMEOUT, ""); return true; fail: return false; } bool econnman_service_disconnect(struct Connman_Service *cs, Econnman_Simple_Cb cb, void *data) { struct connection_data *cd; EINA_SAFETY_ON_NULL_RETURN_VAL(cs, false); if (cs->pending.connect || cs->pending.disconnect || cs->pending.remov) { ERR("Pending connection: connect=%p disconnect=%p remov=%p", cs->pending.connect, cs->pending.disconnect, cs->pending.remov); return false; } cd = calloc(1, sizeof(*cd)); EINA_SAFETY_ON_NULL_GOTO(cd, fail); cd->cs = cs; cd->cb = cb; cd->user_data = data; cs->pending.connect = eldbus_proxy_call(cs->service_iface, "Disconnect", _service_connection_cb, cd, -1, ""); return true; fail: return false; } bool econnman_service_remove(struct Connman_Service *cs, Econnman_Simple_Cb cb, void *data) { struct connection_data *cd; EINA_SAFETY_ON_NULL_RETURN_VAL(cs, false); if (cs->pending.connect || cs->pending.disconnect || cs->pending.remov) { ERR("Pending connection: connect=%p disconnect=%p remov=%p", cs->pending.connect, cs->pending.disconnect, cs->pending.remov); return false; } cd = calloc(1, sizeof(*cd)); EINA_SAFETY_ON_NULL_GOTO(cd, fail); cd->cs = cs; cd->cb = cb; cd->user_data = data; cs->pending.remov = eldbus_proxy_call(cs->service_iface, "Remove", _service_connection_cb, cd, -1, ""); return true; fail: return false; } static struct Connman_Service *_manager_find_service_stringshared( struct Connman_Manager *cm, const char *path) { struct Connman_Service *cs, *found = NULL; EINA_INLIST_FOREACH(cm->services, cs) { if (cs->path == path) { found = cs; break; } } return found; } struct Connman_Service *econnman_manager_find_service(struct Connman_Manager *cm, const char *path) { struct Connman_Service *cs; path = eina_stringshare_add(path); cs = _manager_find_service_stringshared(cm, path); eina_stringshare_del(path); return cs; } static void _manager_services_remove(struct Connman_Manager *cm, Eldbus_Message_Iter *array) { const char *path; while (eldbus_message_iter_get_and_next(array, 'o', &path)) { struct Connman_Service *cs; cs = econnman_manager_find_service(cm, path); if (cs == NULL) { ERR("Received object path '%s' to remove, but it's not in list", path); continue; } cm->services = eina_inlist_remove(cm->services, EINA_INLIST_GET(cs)); DBG("Removed service: %p %s", cs, path); _service_free(cs); } } static void _manager_services_changed(void *data, const Eldbus_Message *msg) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *changed, *removed, *s; Eina_Inlist *tmp = NULL; if (cm->pending.get_services) return; if (!eldbus_message_arguments_get(msg, "a(oa{sv})ao", &changed, &removed)) { ERR("Error getting arguments"); return; } _manager_services_remove(cm, removed); while (eldbus_message_iter_get_and_next(changed, 'r', &s)) { struct Connman_Service *cs; const char *path; Eldbus_Message_Iter *array; if (!eldbus_message_iter_arguments_get(s, "oa{sv}", &path, &array)) continue; cs = econnman_manager_find_service(cm, path); if (!cs) { cs = _service_new(path, array); DBG("Added service: %p %s", cs, path); } else { _service_prop_dict_changed(cs, array); cm->services = eina_inlist_remove(cm->services, EINA_INLIST_GET(cs)); DBG("Changed service: %p %s", cs, path); } tmp = eina_inlist_append(tmp, EINA_INLIST_GET(cs)); } cm->services = tmp; econnman_mod_services_changed(cm); } static void _manager_get_services_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *array, *s; const char *name, *text; cm->pending.get_services = NULL; if (eldbus_message_error_get(msg, &name, &text)) { ERR("Could not get services. %s: %s", name, text); return; } DBG("cm->services=%p", cm->services); if (!eldbus_message_arguments_get(msg, "a(oa{sv})", &array)) { ERR("Error getting array"); return; } while (eldbus_message_iter_get_and_next(array, 'r', &s)) { struct Connman_Service *cs; const char *path; Eldbus_Message_Iter *inner_array; if (!eldbus_message_iter_arguments_get(s, "oa{sv}", &path, &inner_array)) continue; cs = _service_new(path, inner_array); if (cs == NULL) continue; cm->services = eina_inlist_append(cm->services, EINA_INLIST_GET(cs)); DBG("Added service: %p %s", cs, path); } econnman_mod_services_changed(cm); } static bool _manager_parse_prop_changed(struct Connman_Manager *cm, const char *name, Eldbus_Message_Iter *value) { if (strcmp(name, "State") == 0) { const char *state; if (!eldbus_message_iter_arguments_get(value, "s", &state)) return false; DBG("New state: %s", state); cm->state = str_to_state(state); } else if (strcmp(name, "OfflineMode") == 0) { if (!eldbus_message_iter_arguments_get(value, "b", &cm->offline_mode)) return false; } else return false; econnman_mod_manager_update(cm); return true; } static void _manager_prop_changed(void *data, const Eldbus_Message *msg) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *var; const char *name; if (!eldbus_message_arguments_get(msg, "sv", &name, &var)) { ERR("Could not parse message %p", msg); return; } _manager_parse_prop_changed(cm, name, var); } static void _manager_get_prop_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *array, *dict; const char *name, *text; if (eldbus_message_error_get(msg, &name, &text)) { ERR("Could not get properties. %s: %s", name, text); return; } if (!eldbus_message_arguments_get(msg, "a{sv}", &array)) { ERR("Error getting arguments."); return; } while (eldbus_message_iter_get_and_next(array, 'e', &dict)) { const char *key; Eldbus_Message_Iter *var; if (!eldbus_message_iter_arguments_get(dict, "sv", &key, &var)) continue; _manager_parse_prop_changed(cm, key, var); } } static bool _manager_parse_wifi_prop_changed(struct Connman_Manager *cm, const char *name, Eldbus_Message_Iter *value) { if (!strcmp(name, "Powered")) return eldbus_message_iter_arguments_get(value, "b", &cm->powered); else return false; econnman_mod_manager_update(cm); return true; } static void _manager_wifi_prop_changed(void *data, const Eldbus_Message *msg) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *var; const char *name; if (!eldbus_message_arguments_get(msg, "sv", &name, &var)) { ERR("Could not parse message %p", msg); return; } _manager_parse_wifi_prop_changed(cm, name, var); } static void _manager_get_wifi_prop_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { struct Connman_Manager *cm = data; Eldbus_Message_Iter *array, *dict; const char *name, *message; cm->pending.get_wifi_properties = NULL; if (eldbus_message_error_get(msg, &name, &message)) { ERR("Could not get properties. %s: %s", name, message); return; } if (!eldbus_message_arguments_get(msg, "a{sv}", &array)) { ERR("Error getting arguments."); return; } while (eldbus_message_iter_get_and_next(array, 'e', &dict)) { Eldbus_Message_Iter *var; const char *key; if (!eldbus_message_iter_arguments_get(dict, "sv", &key, &var)) continue; _manager_parse_wifi_prop_changed(cm, key, var); } } static void _manager_agent_unregister(struct Connman_Manager *cm) { eldbus_proxy_call(cm->manager_iface, "UnregisterAgent", NULL, NULL, -1, "o", AGENT_PATH); } static void _manager_agent_register_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { const char *name, *text; if (eldbus_message_error_get(msg, &name, &text)) { WRN("Could not register agent. %s: %s", name, text); return; } INF("Agent registered"); } static void _manager_agent_register(struct Connman_Manager *cm) { if (!cm) return; eldbus_proxy_call(cm->manager_iface, "RegisterAgent", _manager_agent_register_cb, NULL, -1, "o", AGENT_PATH); } static void _manager_free(struct Connman_Manager *cm) { Eldbus_Object *obj; if (!cm) return; while (cm->services) { struct Connman_Service *cs = EINA_INLIST_CONTAINER_GET(cm->services, struct Connman_Service); cm->services = eina_inlist_remove(cm->services, cm->services); _service_free(cs); } if (cm->pending.get_services) eldbus_pending_cancel(cm->pending.get_services); if (cm->pending.get_wifi_properties) eldbus_pending_cancel(cm->pending.get_wifi_properties); if (cm->pending.set_powered) eldbus_pending_cancel(cm->pending.set_powered); eina_stringshare_del(cm->path); obj = eldbus_proxy_object_get(cm->manager_iface); eldbus_proxy_unref(cm->manager_iface); eldbus_object_unref(obj); obj = eldbus_proxy_object_get(cm->technology_iface); eldbus_proxy_unref(cm->technology_iface); eldbus_object_unref(obj); free(cm); } static void _manager_powered_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { Eldbus_Pending *p; struct Connman_Manager *cm = data; const char *error_name, *error_msg; cm->pending.set_powered = NULL; if (eldbus_message_error_get(msg, &error_name, &error_msg)) { ERR("Error: %s %s", error_name, error_msg); return; } if (cm->pending.get_wifi_properties) eldbus_pending_cancel(cm->pending.get_wifi_properties); p = eldbus_proxy_call(cm->technology_iface, "GetProperties", _manager_get_wifi_prop_cb, cm, -1, ""); cm->pending.get_wifi_properties = p; } void econnman_powered_set(struct Connman_Manager *cm, Eina_Bool powered) { Eldbus_Message_Iter *main_iter, *var; Eldbus_Message *msg; if (cm->pending.set_powered) eldbus_pending_cancel(cm->pending.set_powered); msg = eldbus_proxy_method_call_new(cm->technology_iface, "SetProperty"); main_iter = eldbus_message_iter_get(msg); eldbus_message_iter_basic_append(main_iter, 's', "Powered"); var = eldbus_message_iter_container_new(main_iter, 'v', "b"); eldbus_message_iter_basic_append(var, 'b', powered); eldbus_message_iter_container_close(main_iter, var); cm->pending.set_powered = eldbus_proxy_send(cm->technology_iface, msg, _manager_powered_cb, cm, -1); } static struct Connman_Manager *_manager_new(void) { const char *path = "/"; struct Connman_Manager *cm; Eldbus_Object *obj; cm = calloc(1, sizeof(*cm)); EINA_SAFETY_ON_NULL_RETURN_VAL(cm, NULL); obj = eldbus_object_get(conn, CONNMAN_BUS_NAME, "/"); cm->manager_iface = eldbus_proxy_get(obj, CONNMAN_MANAGER_IFACE); obj = eldbus_object_get(conn, CONNMAN_BUS_NAME, "/net/connman/technology/wifi"); cm->technology_iface = eldbus_proxy_get(obj, CONNMAN_TECHNOLOGY_IFACE); cm->path = eina_stringshare_add(path); eldbus_proxy_signal_handler_add(cm->manager_iface, "PropertyChanged", _manager_prop_changed, cm); eldbus_proxy_signal_handler_add(cm->manager_iface, "ServicesChanged", _manager_services_changed, cm); eldbus_proxy_signal_handler_add(cm->technology_iface, "PropertyChanged", _manager_wifi_prop_changed, cm); /* * PropertyChanged signal in service's path is guaranteed to arrive only * after ServicesChanged above. So we only add the handler later, in a per * service manner. */ cm->pending.get_services = eldbus_proxy_call(cm->manager_iface, "GetServices", _manager_get_services_cb, cm, -1, ""); eldbus_proxy_call(cm->manager_iface, "GetProperties", _manager_get_prop_cb, cm, -1, ""); cm->pending.get_wifi_properties = eldbus_proxy_call(cm->technology_iface, "GetProperties", _manager_get_wifi_prop_cb, cm, -1, ""); return cm; } static inline void _e_connman_system_name_owner_exit(void) { if (!connman_manager) return; _manager_agent_unregister(connman_manager); econnman_mod_manager_inout(NULL); _manager_free(connman_manager); connman_manager = NULL; ecore_event_add(E_CONNMAN_EVENT_MANAGER_OUT, NULL, NULL, NULL); } static inline void _e_connman_system_name_owner_enter(const char *owner) { connman_manager = _manager_new(); _manager_agent_register(connman_manager); ecore_event_add(E_CONNMAN_EVENT_MANAGER_IN, NULL, NULL, NULL); econnman_mod_manager_inout(connman_manager); } static void _e_connman_system_name_owner_changed(void *data, const char *bus, const char *from, const char *to) { if (to[0]) _e_connman_system_name_owner_enter(to); else _e_connman_system_name_owner_exit(); } /** * Initialize E Connection Manager (E_Connman) system. * * This will connect to ConnMan through DBus and watch for it going in and out. * * Interesting events are: * - E_CONNMAN_EVENT_MANAGER_IN: issued when connman is avaiable. * - E_CONNMAN_EVENT_MANAGER_OUT: issued when connman connection is lost. */ unsigned int e_connman_system_init(Eldbus_Connection *eldbus_conn) { init_count++; if (init_count > 1) return init_count; E_CONNMAN_EVENT_MANAGER_IN = ecore_event_type_new(); E_CONNMAN_EVENT_MANAGER_OUT = ecore_event_type_new(); conn = eldbus_conn; eldbus_name_owner_changed_callback_add(conn, CONNMAN_BUS_NAME, _e_connman_system_name_owner_changed, NULL, EINA_TRUE); agent = econnman_agent_new(eldbus_conn); return init_count; } /** * Shutdown ConnMan system * * When count drops to 0 resources will be released and no calls should be * made anymore. */ unsigned int e_connman_system_shutdown(void) { if (init_count == 0) { ERR("connman system already shut down."); return 0; } init_count--; if (init_count > 0) return init_count; eldbus_name_owner_changed_callback_del(conn, CONNMAN_BUS_NAME, _e_connman_system_name_owner_changed, NULL); _e_connman_system_name_owner_exit(); if (agent) econnman_agent_del(agent); if (conn) eldbus_connection_unref(conn); agent = NULL; conn = NULL; E_CONNMAN_EVENT_MANAGER_OUT = 0; E_CONNMAN_EVENT_MANAGER_IN = 0; return init_count; }