diff --git a/configure.ac b/configure.ac index c9cacb795a..8b030241ee 100644 --- a/configure.ac +++ b/configure.ac @@ -4374,6 +4374,42 @@ EFL_EVAL_PKGS([ELUA]) EFL_LIB_END_OPTIONAL([Elua]) #### End of Elua +#### Elocation + +EFL_LIB_START([Elocation]) + +### Default values + +### Additional options to configure + +### Checks for programs + +### Checks for libraries +EFL_PLATFORM_DEPEND([ELOCATION], [evil]) +EFL_INTERNAL_DEPEND_PKG([ELOCATION], [eina]) +EFL_INTERNAL_DEPEND_PKG([ELOCATION], [eo]) +EFL_INTERNAL_DEPEND_PKG([ELOCATION], [ecore]) +EFL_INTERNAL_DEPEND_PKG([ELOCATION], [eldbus]) + +EFL_ADD_LIBS([ELOCATION], [-lm]) + +### Checks for header files + +### Checks for types + +### Checks for structures + +### Checks for compiler characteristics + +### Checks for linker characteristics + +### Checks for library functions + +### Check availability + +EFL_LIB_END([Elocation]) +#### End of Elocation + ### Add Wayland server library if test is enabled if test "x${want_tests}" = "xyes" -a "x${want_wayland}" = "xyes"; then EFL_DEPEND_PKG([ECORE_WAYLAND_SRV], [WAYLAND], [wayland-server >= 1.3.0]) @@ -4496,6 +4532,7 @@ pc/edje-cxx.pc pc/emotion.pc pc/ethumb.pc pc/ethumb_client.pc +pc/elocation.pc dbus-services/org.enlightenment.Efreet.service dbus-services/org.enlightenment.Ethumb.service systemd-services/efreet.service diff --git a/pc/elocation.pc.in b/pc/elocation.pc.in new file mode 100644 index 0000000000..5b6e52bb3d --- /dev/null +++ b/pc/elocation.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: elocation +Description: Enlightenment location library +@pkgconfig_requires_private@: @requirements_elocation@ +Version: @VERSION@ +Libs: -L${libdir} -lelocation +Libs.private: -lm +Cflags: -I${includedir}/elocation-@VMAJ@ diff --git a/src/Makefile.am b/src/Makefile.am index fdb13a2976..72830d52ac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,8 +78,8 @@ include Makefile_Eolian_Cxx.am include Makefile_Eet_Cxx.am include Makefile_Eo_Cxx.am include Makefile_Efl_Cxx.am - include Makefile_Elua.am +include Makefile_Elocation.am .PHONY: benchmark examples diff --git a/src/Makefile_Elocation.am b/src/Makefile_Elocation.am new file mode 100644 index 0000000000..0fc5b696ed --- /dev/null +++ b/src/Makefile_Elocation.am @@ -0,0 +1,16 @@ +### Library + +lib_LTLIBRARIES += lib/elocation/libelocation.la + +installed_elocationsmainheadersdir = $(includedir)/elocation-@VMAJ@ +dist_installed_elocationsmainheaders_DATA = \ +lib/elocation/Elocation.h \ +lib/elocation/elocation_private.h + +lib_elocation_libelocation_la_SOURCES = \ +lib/elocation/elocation.c + +lib_elocation_libelocation_la_CPPFLAGS = -I$(top_builddir)/src/lib/efl @ELOCATION_CFLAGS@ +lib_elocation_libelocation_la_LIBADD = @ELOCATION_LIBS@ +lib_elocation_libelocation_la_DEPENDENCIES = @ELOCATION_INTERNAL_LIBS@ +lib_elocation_libelocation_la_LDFLAGS = @EFL_LTLIBRARY_FLAGS@ diff --git a/src/lib/elocation/Elocation.h b/src/lib/elocation/Elocation.h new file mode 100644 index 0000000000..851b679172 --- /dev/null +++ b/src/lib/elocation/Elocation.h @@ -0,0 +1,418 @@ +/** + * @brief Elocation Library + * + * @mainpage Elocation + * @version 0.0.0 + * @author Stefan Schmidt + * @date 2012 + * + * @section intro Elocation Use Cases + * + * Elocation is meant as a convenience library to ease application developers + * the usage of geo information in their apps. Adding a geo tag to a picture or + * translating an address to a GPS position and show it on a map widget are just + * some of the use cases. + * + * In the beginning elocation will rely on the GeoClue DBus service. Its has + * providers for various techniques to get hold off the current position. + * Ranging from GeoIP over wifi and GSM cell location to GPS. As well as + * provider to translates between location in a textual form to coordinates + * (GeoCode). + * + * Elocation covers all of these interfaces but in the end it depends on your + * system and the installed GeoClue providers what can be used. + * + * Currently it offer the following functionality: + * @li Request current address in textual form + * @li Request current position in GPS format + * @li Translate a position into and address or an address in a position + * + * You can find the API documentation at @ref Location +*/ +#ifndef _ELOCATION_H +#define _ELOCATION_H + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef _WIN32 +# ifdef EFL_ECORE_BUILD +# ifdef DLL_EXPORT +# define EAPI __declspec(dllexport) +# else +# define EAPI +# endif /* ! DLL_EXPORT */ +# else +# define EAPI __declspec(dllimport) +# endif /* ! EFL_ECORE_BUILD */ +#else +# ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +# else +# define EAPI +# endif +#endif /* ! _WIN32 */ + +#include + +#include +#include + +/** + * @file Elocation.h + * + * @defgroup Location Location + */ + +/** + * @ingroup Location + * @brief Available location events that are emitted from the library + * @since 1.8 + * + * Ecore events emitted by the library. Applications can register ecore event + * handlers to react on such events. After the initial query this can be used + * to keep track of changes and update your UI or data accordingly. + * @{ + */ +EAPI extern int ELOCATION_EVENT_STATUS; /**< Status changed */ +EAPI extern int ELOCATION_EVENT_POSITION; /**< Position changed */ +EAPI extern int ELOCATION_EVENT_ADDRESS; /**< Address changed */ +EAPI extern int ELOCATION_EVENT_VELOCITY; /**< Velocity changed */ +EAPI extern int ELOCATION_EVENT_GEOCODE; /**< Reply for geocode translation arrived */ +EAPI extern int ELOCATION_EVENT_REVERSEGEOCODE; /**< Reply for geocode translation arrived */ +EAPI extern int ELOCATION_EVENT_NMEA; /**< NMEA update */ +EAPI extern int ELOCATION_EVENT_SATELLITE; /**< Satellite info changed */ +EAPI extern int ELOCATION_EVENT_POI; /**< POI reply */ +EAPI extern int ELOCATION_EVENT_META_READY; /**< Meta provider is ready to be used */ +/**@}*/ + +/** + * @ingroup Location + * @typedef Elocation_Accuracy_Level + * @since 1.8 + * + * Different location accuracy levels from country level up to detailed, + * e.g. GPS, information. + * @{ + */ +typedef enum { + ELOCATION_ACCURACY_LEVEL_NONE = 0, + ELOCATION_ACCURACY_LEVEL_COUNTRY, + ELOCATION_ACCURACY_LEVEL_REGION, + ELOCATION_ACCURACY_LEVEL_LOCALITY, + ELOCATION_ACCURACY_LEVEL_POSTALCODE, + ELOCATION_ACCURACY_LEVEL_STREET, + ELOCATION_ACCURACY_LEVEL_DETAILED, +} Elocation_Accuracy_Level; +/**@}*/ + +/** + * @ingroup Location + * @typedef Elocation_Resource_Flags + * @since 1.8 + * + * Flags for available system resources to be used for locating. So far they + * cover physical resources like network connection, cellular network + * connection and GPS. + * @{ + */ +typedef enum { + ELOCATION_RESOURCE_NONE = 0, + ELOCATION_RESOURCE_NETWORK = 1 << 0, /**< Internet connection is available */ + ELOCATION_RESOURCE_CELL = 1 << 1, /**< Cell network information, e.g. GSM, is available */ + ELOCATION_RESOURCE_GPS = 1 << 2, /**< GPS information is available */ + + ELOCATION_RESOURCE_ALL = (1 << 10) - 1 /**< All resources are available */ +} Elocation_Resource_Flags; +/**@}*/ + +/** + * @ingroup Location + * @typedef Elocation_Accuracy + * @since 1.8 + * + * Information about the accuracy of the reported location. For details about + * the level of accuracy see #Elocation_Accuracy_Level. It also covers + * horizontal and vertical accuracy. The values depend on the used provider + * and may very in quality. + */ +typedef struct _Elocation_Accuracy +{ + Elocation_Accuracy_Level level; + double horizontal; + double vertical; +} Elocation_Accuracy; + +/** + * @ingroup Location + * @typedef Elocation_Address + * @since 1.8 + * + * Location information in textual form. Depending on the used provider this + * can cover only the country or a detailed address with postcode and street. + * The level of detail varies depending on the used provider. + * A timestamp is available to calculate the age of the address data. + */ +typedef struct _Elocation_Address +{ + unsigned int timestamp; /**< Timestamp of data read out in seconds since epoch */ + char *country; + char *countrycode; + char *locality; + char *postalcode; + char *region; + char *timezone; + Elocation_Accuracy *accur; +} Elocation_Address; + +/** + * @ingroup Location + * @typedef Elocation_Position + * @since 1.8 + * + * Location information based on the GPS grid. Latitude, longitude and altitude. + * A timestamp is available to calculate the age of the address data. + */ +typedef struct _Elocation_Postion +{ + unsigned int timestamp; /**< Timestamp of data read out in seconds since epoch */ + double latitude; + double longitude; + double altitude; + Elocation_Accuracy *accur; +} Elocation_Position; + +/** + * @ingroup Location + * @typedef Elocation_Velocity + * @since 1.8 + * + * Velocity information. So far this interface is only offered with GPS based + * providers. It offers information about speed, direction and climb. + * A timestamp is available to calculate the age of the address data. + * + * FIXME: check units and formats of this values coming in from GeoClue + */ +typedef struct _Elocation_Velocity +{ + unsigned int timestamp; /**< Timestamp of data read out in seconds since epoch */ + double speed; + double direction; + double climb; +} Elocation_Velocity; + +/** + * @ingroup Location + * @typedef Elocation_Requirements + * @since 1.8 + * + * Requirement settings for the location provider. Requirements can be a level + * of accuracy or allowed resources like network access or GPS. See + * #Elocation_Resource_Flags for all available resource flags. + * + * Based on this setting the best provider is chosen between the available + * providers of GeoClue. + */ +typedef struct _Elocation_Requirements +{ + Elocation_Accuracy_Level accurancy_level; + int min_time; /**< Minimal time between updates. Not implemented upstream */ + Eina_Bool require_update; + Elocation_Resource_Flags allowed_resources; +} Elocation_Requirements; + +/** + * @brief Create a new address object to operate on. + * @return Address object. + * + * The returned address object is safe to be operated on. It can be used for + * all other elocation functions. Once finished with it it need to be destroyed + * with a call to #elocation_address_free. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Elocation_Address *elocation_address_new(void); + +/** + * @brief Free an address object + * @param address Address object to be freed. + * + * Destroys an address object created with #elocation_address_new. Should be + * used during the cleanup of the application or whenever the address object is + * no longer needed. + * + * @ingroup Location + * @since 1.8 + */ +EAPI void elocation_address_free(Elocation_Address *address); + +/** + * @brief Create a new position object to operate on. + * @return Position object. + * + * The returned address object is safe to be operated on. It can be used for + * all other elocation functions. Once finished with it it need to be destroyed + * with a call to #elocation_address_free. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Elocation_Position *elocation_position_new(void); + +/** + * @brief Free an position object + * @param position Position object to be freed. + * + * Destroys a position object created with #elocation_address_new. Should be + * used during the cleanup of the application or whenever the location object is + * no longer needed. + * + * @ingroup Location + * @since 1.8 + */ +EAPI void elocation_position_free(Elocation_Position *position); + +/** + * @brief Get the current address information. + * @param address Address struct to be filled with information. + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Request the latest address. The requested to the underling components might + * be asynchronous so better check the timestamp if the data has changed. To get + * events when the address changes one can also subscribe to the + * #ELOCATION_EVENT_ADDRESS ecore event which will have the address object as + * event. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_address_get(Elocation_Address *address); + +/** + * @brief Get the current position information. + * @param position Position struct to be filled with information. + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Request the latest position. The requested to the underling components might + * be asynchronous so better check the timestamp if the data has changed. To get + * events when the position changes one can also subscribe to the + * #ELOCATION_EVENT_POSITION ecore event which will have the position object as + * event. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_position_get(Elocation_Position *position); + +/** + * @brief Get the current status. + * @param status Status + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_status_get(int *status); + +/** + * @brief Set the requirements. + * @param requirements Requirements + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Set the requirements for selecting a provider. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_requirements_set(Elocation_Requirements *requirements); + +/** + * @brief Convert position to address + * @param position_shadow Position input + * @param address_shadow Address output + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Use a GeoCode provider to translate from a given GPS coordinate + * representation of a location to a representation in textual form. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_position_to_address(Elocation_Position *position_shadow, Elocation_Address *address_shadow); + +/** + * @brief Convert address to position + * @param address_shadow Address input + * @param position_shadow Position output + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Use a GeoCode provider to translate from a given textual form + * representation of a location to a representation as GPS coordinates. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_address_to_position(Elocation_Address *address_shadow, Elocation_Position *position_shadow); + +/** + * @brief Convert free form address tring to position + * @param freeform_address Address string in free form + * @param position_shadow Position output + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Similar GeoCode translation from textual form to GPS coordinates as + * #elocation_address_to_position but in this case the address is a simple + * string which hopefully contains enough information for the provider to + * understand and translate. + * + * Useful for an easy search interface in an application but also more error + * prone regarding correct results. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_freeform_address_to_position(const char *freeform_address, Elocation_Position *position_shadow); + +/** + * @brief Request a landmark position + * @param position_shadow Position ouput + * @param address_shadow Address input + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * Request a landmark position also known as Point Of Interest (POI) from + * GeoClue. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_landmarks_get(Elocation_Position *position_shadow, Elocation_Address *address_shadow); + +/** + * @brief Initialize the elocation subsystem. + * @return EINA_TRUE for success and EINA_FALSE for failure. + * + * This function must be called before using any of the Elocation functionality + * in your application to make sure it it setup correctly for usage. + * + * @ingroup Location + * @since 1.8 + */ +EAPI Eina_Bool elocation_init(void); + +/** + * @brief Cleanup and shutdown the elocation subsystem. + * + * This function must be called when the application is no longer using any of + * the Elocation functionality to allow the subsystem to shutdown cleanly. + * + * @ingroup Location + * @since 1.8 + */ +EAPI void elocation_shutdown(void); +#endif diff --git a/src/lib/elocation/elocation.c b/src/lib/elocation/elocation.c new file mode 100644 index 0000000000..a14d19e483 --- /dev/null +++ b/src/lib/elocation/elocation.c @@ -0,0 +1,1416 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +/* FIXME: These globals really need to get reduced before leaving the PROTO + * area. + */ +static char *unique_name = NULL; +static Eldbus_Connection *conn = NULL; +static Elocation_Provider *address_provider = NULL; +static Elocation_Provider *position_provider = NULL; +static Eldbus_Object *obj_meta = NULL; +static Eldbus_Proxy *manager_master = NULL; +static Eldbus_Proxy *meta_geoclue = NULL; +static Eldbus_Proxy *meta_address = NULL; +static Eldbus_Proxy *meta_position = NULL; +static Eldbus_Proxy *meta_masterclient = NULL; +static Eldbus_Proxy *meta_velocity = NULL; +static Eldbus_Proxy *meta_nmea = NULL; +static Eldbus_Proxy *meta_satellite = NULL; +static Eldbus_Proxy *geonames_geocode = NULL; +static Eldbus_Proxy *geonames_rgeocode = NULL; +static Eldbus_Proxy *master_poi = NULL; +static Elocation_Address *address = NULL; +static Elocation_Position *position = NULL; +static Elocation_Address *addr_geocode = NULL; +static Elocation_Position *pos_geocode = NULL; +static Elocation_Velocity *velocity = NULL; +static int *status = -1; /* 0 is a valid status code */ +static char nmea_sentence[256]; + +int _elocation_log_dom = -1; + +/* Elocation ecore event types we provide to the application. */ +EAPI int ELOCATION_EVENT_IN; +EAPI int ELOCATION_EVENT_OUT; +EAPI int ELOCATION_EVENT_STATUS; +EAPI int ELOCATION_EVENT_POSITION; +EAPI int ELOCATION_EVENT_ADDRESS; +EAPI int ELOCATION_EVENT_VELOCITY; +EAPI int ELOCATION_EVENT_GEOCODE; +EAPI int ELOCATION_EVENT_REVERSEGEOCODE; +EAPI int ELOCATION_EVENT_NMEA; +EAPI int ELOCATION_EVENT_SATELLITE; +EAPI int ELOCATION_EVENT_POI; +EAPI int ELOCATION_EVENT_META_READY; + +static void +_dummy_free(void *user_data, void *func_data) +{ + /* Don't free the event data after dispatching the event. We keep track of + * it on our own + */ +} + +/* Generic provider message unmarshaller. Used from all different provider + * calbacks that receive such a message + */ +static Eina_Bool +unmarshall_provider(const Eldbus_Message *reply, Elocation_Provider *provider) +{ + char *name = NULL, *desc = NULL, *service = NULL, *path = NULL; + + if (!eldbus_message_arguments_get(reply, "ssss", &name, &desc, &service, &path)) + return EINA_FALSE; + + provider->name = strdup(name); + provider->description = strdup(desc); + provider->service = strdup(service); + provider->path = strdup(path); + return EINA_TRUE; +} + +static void +meta_address_provider_info_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + Elocation_Provider *addr_provider; + + addr_provider = data; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_provider(reply, addr_provider)) + { + ERR("Error: Unable to unmarshall address provider"); + return; + } + + DBG("Meta address provider name: %s, %s, %s, %s", addr_provider->name, + addr_provider->description, + addr_provider->service, + addr_provider->path); +} + +static void +meta_position_provider_info_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + Elocation_Provider *pos_provider; + + pos_provider = data; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_provider(reply, pos_provider)) + { + ERR("Error: Unable to unmarshall position provider"); + return; + } + + DBG("Meta position provider name: %s, %s, %s, %s", pos_provider->name, + pos_provider->description, + pos_provider->service, + pos_provider->path); +} + +static void +meta_address_provider_info_signal_cb(void *data, const Eldbus_Message *reply) +{ + Elocation_Provider *addr_provider; + addr_provider = data; + + if (!unmarshall_provider(reply, addr_provider)) + { + ERR("Error: Unable to unmarshall address provider"); + return; + } + + DBG("Meta address provider name changed: %s, %s, %s, %s", addr_provider->name, + addr_provider->description, + addr_provider->service, + addr_provider->path); +} + +static void +meta_position_provider_info_signal_cb(void *data, const Eldbus_Message *reply) +{ + Elocation_Provider *pos_provider; + pos_provider = data; + + if (!unmarshall_provider(reply, pos_provider)) + { + ERR("Error: Unable to unmarshall position provider"); + return; + } + + DBG("Meta position provider name changed: %s, %s, %s, %s", pos_provider->name, + pos_provider->description, + pos_provider->service, + pos_provider->path); +} + +/* A address is quite flexible what kind of key value pairs it contains in the + * dict. Similar to a reverse GeoCode message as both return an address object. + */ +static Eina_Bool +unmarshall_address(const Eldbus_Message *reply, Elocation_Address *addr) +{ + int32_t level, timestamp; + Eldbus_Message_Iter *sub, *dict, *entry; + double horizontal; + double vertical; + const char *key, *signature; + char *value; + + signature = eldbus_message_signature_get(reply); + + if (!strcmp(signature, "ia{ss}(idd)")) + { + if (!eldbus_message_arguments_get(reply, "ia{ss}(idd)", ×tamp, &dict, &sub)) + return EINA_FALSE; + addr->timestamp = timestamp; + } + else if (!strcmp(signature, "a{ss}(idd)")) + { + if (!eldbus_message_arguments_get(reply, "a{ss}(idd)", &dict, &sub)) + return EINA_FALSE; + addr->timestamp = 0; + } + else + return EINA_FALSE; + + + /* Cleanup potential old entries before re-using */ + addr->country = NULL; + addr->countrycode = NULL; + addr->locality = NULL; + addr->postalcode = NULL; + addr->region = NULL; + addr->timezone = NULL; + + while (eldbus_message_iter_get_and_next(dict, 'e', &entry)) + { + eldbus_message_iter_arguments_get(entry, "ss", &key, &value); + + if (!strcmp(key, "country")) + { + free(addr->country); + addr->country = strdup(value); + } + else if (!strcmp(key, "countrycode")) + { + free(addr->countrycode); + addr->countrycode = strdup(value); + } + else if (!strcmp(key, "locality")) + { + free(addr->locality); + addr->locality = strdup(value); + } + else if (!strcmp(key, "postalcode")) + { + free(addr->postalcode); + addr->postalcode = strdup(value); + } + else if (!strcmp(key, "region")) + { + free(addr->region); + addr->region = strdup(value); + } + else if (!strcmp(key, "timezone")) + { + free(addr->timezone); + addr->timezone = strdup(value); + } + } + + eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical); + addr->accur->level = level; + addr->accur->horizontal = horizontal; + addr->accur->vertical = vertical; + return EINA_TRUE; +} + +/* Receive and unmarshall a reverse GeoCode message. The dict can contain a + * variable set of key value pairs so we need to handle this with care + */ +static void +rgeocode_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_address(reply, addr_geocode)) + { + ERR("Error: Unable to unmarshall address"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_REVERSEGEOCODE, addr_geocode, _dummy_free, NULL); +} + +/* Point of Interest (POI) aka landmark message unmarshalling. Thsi interface is + * not in standard GeoClue but currently a Tizen extension. + */ +static void +poi_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + int32_t count, id, rank; + double lat, lon, bound_left, bound_top, bound_right, bound_bottom; + const char *name, *icon, *house, *road, *village, *suburb, *postcode; + const char *city, *county, *country, *country_code; + Eldbus_Message_Iter *array, *struct_landmark; + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + /* Yeah, its quite a horrible message. The POI interface could use a better design */ + if (!eldbus_message_arguments_get(reply, "ia(iiddddddsssssssssss", &count ,&array)) + return; + + /* TODO re-check that the parameter ordering is what we expect */ + while (eldbus_message_iter_get_and_next(array, 'r', &struct_landmark)) + { + eldbus_message_iter_arguments_get(struct_landmark, "iiddddddsssssssssss", &id, &rank, + &lat, &lon, &bound_left, &bound_top, &bound_right, + &bound_bottom, &name, &icon, &house, &road, + &village, &suburb, &postcode, &city, &county, + &country, &country_code); + DBG("Landmark received: %i, %i, %f, %f, %f, %f, %f, %f, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,", + id, rank, lat, lon, bound_left, bound_top, bound_right, + bound_bottom, name, icon, house, road, village, + suburb, postcode, city, county, country, country_code); + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_POI, NULL, _dummy_free, NULL); +} + +/* Unmarshall a GeoCode message */ +static void +geocode_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + GeocluePositionFields fields; + int32_t level; + double horizontal = 0.0; + double vertical = 0.0; + double latitude = 0.0; + double longitude = 0.0; + double altitude = 0.0; + Eldbus_Message_Iter *sub; + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!eldbus_message_arguments_get(reply, "iddd(idd)", &fields,&latitude, + &longitude, &altitude, &sub)) + return; + + /* GeoClue uses some flags to mark position fields as valid. We set invalid + * fields to 0.0 */ + if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE) + pos_geocode->latitude = latitude; + else + pos_geocode->latitude = 0.0; + + if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) + pos_geocode->longitude = longitude; + else + pos_geocode->longitude = 0.0; + + if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE) + pos_geocode->altitude = altitude; + else + pos_geocode->altitude = 0.0; + + eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical); + pos_geocode->accur->level = level; + pos_geocode->accur->horizontal = horizontal; + pos_geocode->accur->vertical = vertical; + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_GEOCODE, pos_geocode, _dummy_free, NULL); +} + +static void +address_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_address(reply, address)) + { + ERR("Error: Unable to unmarshall address"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_ADDRESS, address, _dummy_free, NULL); +} + +static void +address_signal_cb(void *data, const Eldbus_Message *reply) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_address(reply, address)) + { + ERR("Error: Unable to unmarshall address"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_ADDRESS, address, _dummy_free, NULL); +} + +/* Unmarshall a velocity message. This is only available if we use a GPS + * provider from GeoClue. None of the other providers offer this currently. + */ +static Eina_Bool +unmarshall_velocity(const Eldbus_Message *reply) +{ + GeoclueVelocityFields fields; + int32_t timestamp = 0; + double speed = 0.0; + double direction = 0.0; + double climb = 0.0; + + if (!eldbus_message_arguments_get(reply, "iiddd", &fields, ×tamp, + &speed, &direction, &climb)) + return EINA_FALSE; + + velocity->timestamp = timestamp; + + /* GeoClue uses some flags to mark velocity fields as valid. We set invalid + * fields to 0.0 */ + if (fields & GEOCLUE_VELOCITY_FIELDS_SPEED) + velocity->speed = speed; + else + velocity->speed = 0.0; + + if (fields & GEOCLUE_VELOCITY_FIELDS_DIRECTION) + velocity->direction = direction; + else + velocity->direction = 0.0; + + if (fields & GEOCLUE_VELOCITY_FIELDS_CLIMB) + velocity->climb = climb; + else + velocity->climb = 0.0; + + return EINA_TRUE; +} + +static void +velocity_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + WARN("Warning: %s %s", err, errmsg); + return; + } + + if (!unmarshall_velocity(reply)) + { + ERR("Error: Unable to unmarshall velocity"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_VELOCITY, velocity, _dummy_free, NULL); +} + +static void +velocity_signal_cb(void *data, const Eldbus_Message *reply) +{ + if (!unmarshall_velocity(reply)) + { + ERR("Error: Unable to unmarshall velocity"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_VELOCITY, velocity, _dummy_free, NULL); +} + +/* Unmarshall an raw NMEA message. It conatins a raw NMEA sentence which we can + * pass on to applications that want to use their own NMEA parser. This is not + * reommended. Better use the other interfaces to access the needed data. + * + * This is currently a Tizen only interface and not in GeoClue upstream. + */ +static Eina_Bool +unmarshall_nmea(const Eldbus_Message *reply) +{ + int32_t timestamp = 0; + + if (!eldbus_message_arguments_get(reply, "is", ×tamp, &nmea_sentence)) + return EINA_FALSE; + + return EINA_TRUE; +} + +static void +nmea_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + WARN("Warning: %s %s", err, errmsg); + return; + } + + if (!unmarshall_nmea(reply)) + { + ERR("Error: Unable to unmarshall nmea"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_NMEA, nmea_sentence, _dummy_free, NULL); +} + +static void +nmea_signal_cb(void *data, const Eldbus_Message *reply) +{ + if (!unmarshall_nmea(reply)) + { + ERR("Error: Unable to unmarshall nmea"); + return; + } + + ecore_event_add(ELOCATION_EVENT_NMEA, nmea_sentence, _dummy_free, NULL); +} + +/* Unmarshall a satellite information message. This offers GPS specific + * information about the used satellites and its properties. It can be used for + * applications that rely on GPS and want to show more information like a 3D fix + * or used satellites. + * + * This is currently a Tizen only interface and not available in GeoClue upstream. + */ +static Eina_Bool +unmarshall_satellite(const Eldbus_Message *reply) +{ + int32_t timestamp = 0, satellite_used = 0, satellite_visible = 0; + int32_t snr = 0, elevation = 0, azimuth = 0, prn = 0, used_prn = 0; + Eldbus_Message_Iter *sub_prn, *sub_info, *struct_info; + + if (!eldbus_message_arguments_get(reply, "iiiaia(iiii)", ×tamp, &satellite_used, + &satellite_visible, &sub_prn, &sub_info)) + return EINA_FALSE; + + while (eldbus_message_iter_get_and_next(sub_prn, 'i', &used_prn)) + { + DBG("Satellite used PRN %i", used_prn); + } + + /* TODO re-check that the parameter ordering is what we expect */ + while (eldbus_message_iter_get_and_next(sub_info, 'r', &struct_info)) + { + eldbus_message_iter_arguments_get(struct_info, "iiii", &prn, &elevation, &azimuth, &snr); + DBG("Satellite info %i, %i, %i, %i", prn, elevation, azimuth, snr); + } + + return EINA_TRUE; +} + +static void +satellite_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + WARN("Warning: %s %s", err, errmsg); + return; + } + + if (!unmarshall_satellite(reply)) + { + ERR("Error: Unable to unmarshall satellite"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL); +} + +static void +last_satellite_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + WARN("Warning: %s %s", err, errmsg); + return; + } + + if (!unmarshall_satellite(reply)) + { + ERR("Error: Unable to unmarshall last satellite"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL); +} + +static void +satellite_signal_cb(void *data, const Eldbus_Message *reply) +{ + if (!unmarshall_satellite(reply)) + { + ERR("Error: Unable to unmarshall satellite"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL); +} + +/* Unmarshall position coordination message */ +static Eina_Bool +unmarshall_position(const Eldbus_Message *reply) +{ + GeocluePositionFields fields; + int32_t level, timestamp; + double horizontal = 0.0; + double vertical = 0.0; + double latitude = 0.0; + double longitude = 0.0; + double altitude = 0.0; + Eldbus_Message_Iter *sub; + + if (!eldbus_message_arguments_get(reply, "iiddd(idd)", &fields, ×tamp, + &latitude, &longitude, &altitude, &sub)) + return EINA_FALSE; + + if (!eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical)) + return EINA_FALSE; + + position->timestamp = timestamp; + + /* GeoClue uses some flags to mark position fields as valid. We set invalid + * fields to 0.0 */ + if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE) + position->latitude = latitude; + else + position->latitude = 0.0; + + if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) + position->longitude = longitude; + else + position->longitude = 0.0; + + if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE) + position->altitude = altitude; + else + position->altitude = 0.0; + + position->accur->level = level; + position->accur->horizontal = horizontal; + position->accur->vertical = vertical; + + return EINA_TRUE; +} + +static void +position_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!unmarshall_position(reply)) + { + ERR("Error: Unable to unmarshall position"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_POSITION, position, _dummy_free, NULL); +} + +static void +position_signal_cb(void *data, const Eldbus_Message *reply) +{ + if (!unmarshall_position(reply)) + { + ERR("Error: Unable to unmarshall position"); + return; + } + + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_POSITION, position, _dummy_free, NULL); +} + +static Eina_Bool +geoclue_start(void *data, int ev_type, void *event) +{ + DBG("GeoClue start event at %s", unique_name); + return ECORE_CALLBACK_DONE; +} + +static Eina_Bool +geoclue_stop(void *data, int ev_type, void *event) +{ + DBG("GeoClue stop event"); + return ECORE_CALLBACK_DONE; +} + +static void +_reference_add_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + DBG("Reference added"); +} + +static void +_reference_del_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + /* Dummy callback. We are not waiting for any reply here on shutdown */ +} + +static void +status_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + /* We need this to be malloced to be passed to ecore_event_add. Or provide a dummy free callback. */ + status = malloc(sizeof(*status)); + + if (!eldbus_message_arguments_get(reply,"i", status)) + { + ERR("Error: Unable to unmarshall status"); + return; + } + + address_provider->status = position_provider->status = *status; + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_STATUS, status, NULL, NULL); +} + +static void +status_signal_cb(void *data, const Eldbus_Message *reply) +{ + /* We need this to be malloced to be passed to ecore_event_add. Or provide a dummy free callback. */ + status = malloc(sizeof(*status)); + + if (!eldbus_message_arguments_get(reply,"i", status)) + { + ERR("Error: Unable to unmarshall status"); + return; + } + + address_provider->status = position_provider->status = *status; + /* Send out an event to all interested parties that we have an update */ + ecore_event_add(ELOCATION_EVENT_STATUS, status, NULL, NULL); +} + +static void +_dummy_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) +{ +} + +/* We got notified from GeoClue that the meta-provider we asked for is now + * ready. That means we can finish up our initialization and set up all + * callbacks and handling for the interfaces we use on the meta-provider. + * + * We also call the interfaces to get an initial set of data that we can provide + * to eager aplications. + */ +static void +create_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending) +{ + const char *object_path; + Eina_Bool updates; + int accur_level, min_time, resources; + const char *err, *errmsg; + + if (eldbus_message_error_get(reply, &err, &errmsg)) + { + ERR("Error: %s %s", err, errmsg); + return; + } + + if (!eldbus_message_arguments_get(reply, "o", &object_path)) return; + + DBG("Object path for client: %s", object_path); + + /* With the created object path we now have a meta provider we can operate on. + * Geoclue handles the selection of the best provider internally for the meta + * provider */ + obj_meta = eldbus_object_get(conn, GEOCLUE_DBUS_NAME, object_path); + if (!obj_meta) + { + ERR("Error: could not get object for client"); + return; + } + + meta_geoclue = eldbus_proxy_get(obj_meta, GEOCLUE_GEOCLUE_IFACE); + if (!meta_geoclue) + { + ERR("Error: could not get proxy for geoclue"); + return; + } + + meta_address = eldbus_proxy_get(obj_meta, GEOCLUE_ADDRESS_IFACE); + if (!meta_address) + { + ERR("Error: could not get proxy address"); + return; + } + + meta_position = eldbus_proxy_get(obj_meta, GEOCLUE_POSITION_IFACE); + if (!meta_position) + { + ERR("Error: could not get proxy for position"); + return; + } + + meta_masterclient = eldbus_proxy_get(obj_meta, GEOCLUE_MASTERCLIENT_IFACE); + if (!meta_masterclient) + { + ERR("Error: could not get proxy for master client"); + return; + } + + meta_velocity = eldbus_proxy_get(obj_meta, GEOCLUE_VELOCITY_IFACE); + if (!meta_velocity) + { + ERR("Error: could not get proxy for velocity"); + return; + } + + meta_nmea = eldbus_proxy_get(obj_meta, GEOCLUE_NMEA_IFACE); + if (!meta_nmea) + { + ERR("Error: could not get proxy for nmea"); + return; + } + + meta_satellite = eldbus_proxy_get(obj_meta, GEOCLUE_SATELLITE_IFACE); + if (!meta_satellite) + { + ERR("Error: could not get proxy for satellite"); + return; + } + + /* Send Geoclue a set of requirements we have for the provider and start the address and position + * meta provider afterwards. After this we should be ready for operation. */ + updates = EINA_FALSE; /* Especially the web providers do not offer updates */ + accur_level = ELOCATION_ACCURACY_LEVEL_COUNTRY; + min_time = 0; /* Minimal times between updates (no implemented yet) */ + resources = ELOCATION_RESOURCE_ALL; + + eldbus_proxy_signal_handler_add(meta_masterclient, "AddressProviderChanged", + meta_address_provider_info_signal_cb, address_provider); + eldbus_proxy_signal_handler_add(meta_masterclient, "PositionProviderChanged", + meta_position_provider_info_signal_cb, position_provider); + + if (!eldbus_proxy_call(meta_masterclient, "SetRequirements", _dummy_cb, NULL, -1, "iibi", + accur_level, min_time, updates, resources)) + { + ERR("Error: could not call SetRequirements"); + return; + } + + if (!eldbus_proxy_call(meta_masterclient, "AddressStart", _dummy_cb, NULL, -1, "")) + { + ERR("Error: could not call AddressStart"); + return; + } + + if (!eldbus_proxy_call(meta_masterclient, "PositionStart", _dummy_cb, NULL, -1, "")) + { + ERR("Error: could not call PositionStart"); + return; + } + + if (!eldbus_proxy_call(meta_geoclue, "AddReference", _reference_add_cb, NULL, -1, "")) + { + ERR("Error: could not call AddReference"); + return; + } + + if (!eldbus_proxy_call(meta_address, "GetAddress", address_cb, NULL, -1, "")) + { + ERR("Error: could not call GetAddress"); + return; + } + + if (!eldbus_proxy_call(meta_position, "GetPosition", position_cb, NULL, -1, "")) + { + ERR("Error: could not call GetPosition"); + return; + } + + if (!eldbus_proxy_call(meta_geoclue, "GetStatus", status_cb, NULL, -1, "")) + { + ERR("Error: could not call GetStatus"); + return; + } + + if (!eldbus_proxy_call(meta_velocity, "GetVelocity", velocity_cb, NULL, -1, "")) + { + ERR("Error: could not call GetVelocity"); + return; + } + + if (!eldbus_proxy_call(meta_nmea, "GetNmea", nmea_cb, NULL, -1, "")) + { + ERR("Error: could not call GetNmea"); + return; + } + + if (!eldbus_proxy_call(meta_satellite, "GetSatellite", satellite_cb, NULL, -1, "")) + { + ERR("Error: could not call GetSatellite"); + return; + } + + if (!eldbus_proxy_call(meta_satellite, "GetLastSatellite", last_satellite_cb, NULL, -1, "")) + { + ERR("Error: could not call GetLastSatellite"); + return; + } + + if (!eldbus_proxy_call(meta_masterclient, "GetAddressProvider", meta_address_provider_info_cb, + address_provider, -1, "")) + { + ERR("Error: could not call GetAddressProvider"); + return; + } + + if (!eldbus_proxy_call(meta_masterclient, "GetPositionProvider", meta_position_provider_info_cb, + position_provider, -1, "")) + { + ERR("Error: could not call GetPositionProvider"); + return; + } + + eldbus_proxy_signal_handler_add(meta_address, "AddressChanged", address_signal_cb, NULL); + eldbus_proxy_signal_handler_add(meta_position, "PositionChanged", position_signal_cb, NULL); + eldbus_proxy_signal_handler_add(meta_geoclue, "StatusChanged", status_signal_cb, NULL); + eldbus_proxy_signal_handler_add(meta_velocity, "VelocityChanged", velocity_signal_cb, NULL); + eldbus_proxy_signal_handler_add(meta_nmea, "NmeaChanged", nmea_signal_cb, NULL); + eldbus_proxy_signal_handler_add(meta_satellite, "SatelliteChanged", satellite_signal_cb, NULL); + + ecore_event_add(ELOCATION_EVENT_META_READY, NULL, NULL, NULL); +} + +static void +_name_owner_changed(void *data, const char *bus, const char *old, const char *new) +{ + if (old[0] == '\0' && new[0] != '\0') + { + ecore_event_add(ELOCATION_EVENT_IN, NULL, NULL, NULL); + unique_name = strdup(new); + } + else if (old[0] != '\0' && new[0] == '\0') + { + if (strcmp(unique_name, old) != 0) + WARN("%s was not the known name %s, ignored.", old, unique_name); + else + ecore_event_add(ELOCATION_EVENT_OUT, NULL, NULL, NULL); + } + else + { + DBG("unknow change from %s to %s", old, new); + } +} + +/* Public API function to request a landmarks position based on an address object */ +EAPI Eina_Bool +elocation_landmarks_get(Elocation_Position *position_shadow, Elocation_Address *address_shadow) +{ + Eldbus_Message *msg; + Eldbus_Message_Iter *iter; + const char *keyword = NULL, *lang = NULL, *country_code = NULL; + int limit = 0; + double left= 0.0, top = 0.0, right = 0.0, bottom = 0.0; + + msg = eldbus_proxy_method_call_new(master_poi, "SearchByPosition"); + iter = eldbus_message_iter_get(msg); + eldbus_message_iter_basic_append(iter, 's', keyword); + eldbus_message_iter_basic_append(iter, 's', lang); + eldbus_message_iter_basic_append(iter, 's', country_code); + eldbus_message_iter_basic_append(iter, 'i', limit); + eldbus_message_iter_basic_append(iter, 'd', left); + eldbus_message_iter_basic_append(iter, 'd', top); + eldbus_message_iter_basic_append(iter, 'd', right); + eldbus_message_iter_basic_append(iter, 'd', bottom); + if (!eldbus_proxy_send(master_poi, msg, poi_cb, NULL, -1)) + { + ERR("Error: could not call SearchByPosition"); + eldbus_message_unref(msg); + return EINA_FALSE; + } + + return EINA_TRUE; +} + +/* Public API function to get an address from a position */ +EAPI Eina_Bool +elocation_position_to_address(Elocation_Position *position_shadow, Elocation_Address *address_shadow) +{ + Eldbus_Message *msg; + Eldbus_Message_Iter *iter, *structure; + + msg = eldbus_proxy_method_call_new(geonames_rgeocode, "PositionToAddress"); + iter = eldbus_message_iter_get(msg); + eldbus_message_iter_basic_append(iter, 'd', position_shadow->latitude); + eldbus_message_iter_basic_append(iter, 'd', position_shadow->longitude); + structure = eldbus_message_iter_container_new(iter, 'r', NULL); + eldbus_message_iter_basic_append(structure, 'i', position_shadow->accur->level); + eldbus_message_iter_basic_append(structure, 'd', position_shadow->accur->horizontal); + eldbus_message_iter_basic_append(structure, 'd', position_shadow->accur->vertical); + eldbus_message_iter_container_close(iter, structure); + if (!eldbus_proxy_send(geonames_rgeocode, msg, rgeocode_cb, NULL, -1)) + { + ERR("Error: could not call PositionToAddress"); + eldbus_message_unref(msg); + return EINA_FALSE; + } + + return EINA_TRUE; +} + +/* Public API function to get a position from and address */ +EAPI Eina_Bool +elocation_address_to_position(Elocation_Address *address_shadow, Elocation_Position *position_shadow) +{ + Eldbus_Message *msg; + Eldbus_Message_Iter *iter, *array; + + /* In function macro to generate a key value pair structure for the dict */ + #define ENTRY(key) { #key, address_shadow->key } + struct keyval { + const char *key; + const char *val; + } keyval[] = { + ENTRY(country), + ENTRY(countrycode), + ENTRY(locality), + ENTRY(postalcode), + ENTRY(region), + ENTRY(timezone), + { NULL, NULL } + }; + #undef ENTRY + + struct keyval *k; + + msg = eldbus_proxy_method_call_new(geonames_geocode, "AddressToPosition"); + iter = eldbus_message_iter_get(msg); + + array = eldbus_message_iter_container_new(iter, 'a', "{ss}"); + + for (k = keyval; k && k->key; k++) + { + Eldbus_Message_Iter *entry; + + if (!k->val) continue; + + entry = eldbus_message_iter_container_new(array, 'e', NULL); + eldbus_message_iter_arguments_append(entry, "ss", k->key, k->val); + eldbus_message_iter_container_close(array, entry); + } + + eldbus_message_iter_container_close(iter, array); + + if (!eldbus_proxy_send(geonames_geocode, msg, geocode_cb, NULL, -1)) + { + ERR("Error: could not call AddressToPosition"); + eldbus_message_unref(msg); + return EINA_FALSE; + } + + return EINA_TRUE; +} + +/* Public API function to get the position from a freeform text input style + * address + */ +EAPI Eina_Bool +elocation_freeform_address_to_position(const char *freeform_address, Elocation_Position *position_shadow) +{ + if (!eldbus_proxy_call(geonames_geocode, "FreeformAddressToPosition", geocode_cb, NULL, -1, "s", freeform_address)) + { + ERR("Error: could not call FreeformAddressToPosition"); + return EINA_FALSE; + } + return EINA_TRUE; +} + +/* Public API function to request the current address */ +EAPI Eina_Bool +elocation_address_get(Elocation_Address *address_shadow) +{ + if (!address) return EINA_FALSE; + + address_shadow = address; + return EINA_TRUE; +} + +/* Public API function to request the current position */ +EAPI Eina_Bool +elocation_position_get(Elocation_Position *position_shadow) +{ + if (!position) return EINA_FALSE; + + position_shadow = position; + return EINA_TRUE; +} + +/* Public API function to request the status */ +EAPI Eina_Bool +elocation_status_get(int *status_shadow) +{ + if (status < 0) return EINA_FALSE; + + status_shadow = status; + return EINA_TRUE; +} + +/* Public API function to create a new position object */ +EAPI Elocation_Position * +elocation_position_new(void) +{ + /* Malloc the global struct we operate on here in this lib. This shadows the + * updated data we are giving to the application */ + position = calloc(1, sizeof(Elocation_Position)); + if (!position) return NULL; + + position->accur = calloc(1, sizeof(Elocation_Accuracy)); + if (!position->accur) return NULL; + + return position; +} + +/* Public API function to create an new address object */ +EAPI Elocation_Address * +elocation_address_new(void) +{ + /* Malloc the global struct we operate on here in this lib. This shadows the + * updated data we are giving to the application */ + address = calloc(1, sizeof(Elocation_Address)); + if (!address) return NULL; + + address->accur = calloc(1, sizeof(Elocation_Accuracy)); + if (!address->accur) return NULL; + + return address; +} + +/* Public API function to free an position object */ +EAPI void +elocation_position_free(Elocation_Position *position_shadow) +{ + if (position != position_shadow) + { + ERR("Corrupted position object"); + return; + } + + free(position->accur); + free(position); +} + +/* Public API function to free an address object */ +EAPI void +elocation_address_free(Elocation_Address *address_shadow) +{ + if (address != address_shadow) + { + ERR("Corrupted address object"); + return; + } + + if (address) + { + free(address->country); + free(address->countrycode); + free(address->locality); + free(address->postalcode); + free(address->region); + free(address->timezone); + free(address->accur); + free(address); + } +} + +/* Public API funtion to initialize the elocation library */ +EAPI Eina_Bool +elocation_init(void) +{ + Eldbus_Object *obj_master = NULL; + Eldbus_Object *obj_geonames = NULL; + + if (!eina_init()) return EINA_FALSE; + if (!ecore_init()) return EINA_FALSE; + if (!eldbus_init()) return EINA_FALSE; + + _elocation_log_dom = eina_log_domain_register("elocation", EINA_COLOR_BLUE); + if (_elocation_log_dom < 0) + { + EINA_LOG_ERR("Could not register 'elocation' log domain."); + } + + /* Create objects, one for each kind, we operate on internally */ + address_provider = calloc(1, sizeof(Elocation_Provider)); + position_provider = calloc(1, sizeof(Elocation_Provider)); + + addr_geocode = calloc(1, sizeof(Elocation_Address)); + if (!addr_geocode) return EINA_FALSE; + + addr_geocode->accur = calloc(1, sizeof(Elocation_Accuracy)); + if (!addr_geocode->accur) return EINA_FALSE; + + pos_geocode = calloc(1, sizeof(Elocation_Position)); + if (!pos_geocode) return EINA_FALSE; + + pos_geocode->accur = calloc(1, sizeof(Elocation_Accuracy)); + if (!pos_geocode->accur) return EINA_FALSE; + + conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION); + if (!conn) + { + ERR("Error: could not connect to session bus."); + return EXIT_FAILURE; + } + + /* Create all ecore event types we send out to interested applications */ + if (ELOCATION_EVENT_IN == 0) + ELOCATION_EVENT_IN = ecore_event_type_new(); + + if (ELOCATION_EVENT_OUT == 0) + ELOCATION_EVENT_OUT = ecore_event_type_new(); + + if (ELOCATION_EVENT_STATUS == 0) + ELOCATION_EVENT_STATUS = ecore_event_type_new(); + + if (ELOCATION_EVENT_POSITION == 0) + ELOCATION_EVENT_POSITION = ecore_event_type_new(); + + if (ELOCATION_EVENT_ADDRESS == 0) + ELOCATION_EVENT_ADDRESS = ecore_event_type_new(); + + if (ELOCATION_EVENT_VELOCITY == 0) + ELOCATION_EVENT_VELOCITY = ecore_event_type_new(); + + if (ELOCATION_EVENT_GEOCODE == 0) + ELOCATION_EVENT_GEOCODE = ecore_event_type_new(); + + if (ELOCATION_EVENT_REVERSEGEOCODE == 0) + ELOCATION_EVENT_REVERSEGEOCODE = ecore_event_type_new(); + + if (ELOCATION_EVENT_NMEA == 0) + ELOCATION_EVENT_NMEA = ecore_event_type_new(); + + if (ELOCATION_EVENT_SATELLITE == 0) + ELOCATION_EVENT_SATELLITE = ecore_event_type_new(); + + if (ELOCATION_EVENT_POI == 0) + ELOCATION_EVENT_POI = ecore_event_type_new(); + + if (ELOCATION_EVENT_META_READY == 0) + ELOCATION_EVENT_META_READY = ecore_event_type_new(); + + obj_master= eldbus_object_get(conn, GEOCLUE_DBUS_NAME, GEOCLUE_OBJECT_PATH); + if (!obj_master) + { + ERR("Error: could not get object"); + return EXIT_FAILURE; + } + + manager_master = eldbus_proxy_get(obj_master, GEOCLUE_MASTER_IFACE); + if (!manager_master) + { + ERR("Error: could not get proxy"); + return EXIT_FAILURE; + } + + /* Create a meta provider for all normal use cases. This will allow GeoClue + * to decide which provider is the best for us internally. + * Right now we don't have the functionality in place to specifically request + * a provider but we maybe need this in the future. We will try without it + * for now. + */ + if (!eldbus_proxy_call(manager_master, "Create", create_cb, NULL, -1, "")) + { + ERR("Error: could not call Create"); + return EXIT_FAILURE; + } + + master_poi = eldbus_proxy_get(obj_master, GEOCLUE_POI_IFACE); + if (!master_poi) + { + ERR("Error: could not get proxy"); + return EXIT_FAILURE; + } + + /* Geocode and reverse geocode never show up as meta provider. Still we want + * to be able to convert so we keep them around directly here. */ + obj_geonames= eldbus_object_get(conn, GEONAMES_DBUS_NAME, GEONAMES_OBJECT_PATH); + if (!obj_geonames) + { + ERR("Error: could not get object for geonames"); + return EXIT_FAILURE; + } + + geonames_geocode = eldbus_proxy_get(obj_geonames, GEOCLUE_GEOCODE_IFACE); + if (!geonames_geocode) + { + ERR("Error: could not get proxy"); + return EXIT_FAILURE; + } + + geonames_rgeocode = eldbus_proxy_get(obj_geonames, GEOCLUE_REVERSEGEOCODE_IFACE); + if (!geonames_rgeocode) + { + ERR("Error: could not get proxy"); + return EXIT_FAILURE; + } + + eldbus_name_owner_changed_callback_add(conn, GEOCLUE_DBUS_NAME, _name_owner_changed, + NULL, EINA_TRUE); + + ecore_event_handler_add(ELOCATION_EVENT_IN, geoclue_start, NULL); + ecore_event_handler_add(ELOCATION_EVENT_OUT, geoclue_stop, NULL); + + return EINA_TRUE; +} + +/* Public API function to shutdown the elocation library form the application */ +EAPI void +elocation_shutdown(void) +{ + /* Depending on if the create_cb was successfully received meta_geoclue is + * setup or not. So we * need to check here if this is not the case + */ + if (meta_geoclue) + { + /* To allow geoclue freeing unused providers we free our reference on it here */ + if (!eldbus_proxy_call(meta_geoclue, "RemoveReference", _reference_del_cb, NULL, -1, "")) + { + ERR("Error: could not call RemoveReference"); + } + } + + /* Quite a bit of allocated string and generic memory cleanup. This should be + *less when we went away from all this global var business. + */ + if (address_provider) + { + free(address_provider->name); + free(address_provider->description); + free(address_provider->service); + free(address_provider->path); + free(address_provider); + } + + if (position_provider) + { + free(position_provider->name); + free(position_provider->description); + free(position_provider->service); + free(position_provider->path); + free(position_provider); + } + + if (pos_geocode) + { + free(pos_geocode->accur); + free(pos_geocode); + } + + if (addr_geocode) + { + free(addr_geocode->country); + free(addr_geocode->countrycode); + free(addr_geocode->locality); + free(addr_geocode->postalcode); + free(addr_geocode->region); + free(addr_geocode->timezone); + free(addr_geocode->accur); + free(addr_geocode); + } + + /* Unreference some eldbus strcutures we now longer use. To allow eldbus to + * free them internally. + */ + if (manager_master) + eldbus_proxy_unref(manager_master); + + eldbus_name_owner_changed_callback_del(conn, GEOCLUE_DBUS_NAME, _name_owner_changed, NULL); + eldbus_connection_unref(conn); + eldbus_shutdown(); + ecore_shutdown(); + eina_log_domain_unregister(_elocation_log_dom); + eina_shutdown(); +} diff --git a/src/lib/elocation/elocation_private.h b/src/lib/elocation/elocation_private.h new file mode 100644 index 0000000000..1203cc35b1 --- /dev/null +++ b/src/lib/elocation/elocation_private.h @@ -0,0 +1,181 @@ +#ifndef _ELOCATION_PRIVATE_H +#define _ELOCATION_PRIVATE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#ifndef ELOCATION_COLOR_DEFAULT +#define ELOCATION_COLOR_DEFAULT EINA_COLOR_BLUE +#endif +extern int _elocation_log_dom; +#ifdef CRI +#undef CRI +#endif + +#ifdef ERR +#undef ERR +#endif +#ifdef INF +#undef INF +#endif +#ifdef WARN +#undef WARN +#endif +#ifdef DBG +#undef DBG +#endif + +#define CRI(...) EINA_LOG_DOM_CRIT(_elocation_log_dom, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_elocation_log_dom, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_elocation_log_dom, __VA_ARGS__) +#define WARN(...) EINA_LOG_DOM_WARN(_elocation_log_dom, __VA_ARGS__) +#define ERR(...) EINA_LOG_DOM_ERR(_elocation_log_dom, __VA_ARGS__) + +/* Provider bus names and object paths. Master is the generic one which should + * pick up the best one internally based on given requirements. It is also still + * possible to use providers directly */ +#define GEOCLUE_DBUS_NAME "org.freedesktop.Geoclue.Master" +#define GEOCLUE_OBJECT_PATH "/org/freedesktop/Geoclue/Master" +#define GSMLOC_DBUS_NAME "org.freedesktop.Geoclue.Providers.Gsmloc" +#define GSMLOC_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Gsmloc" +#define HOSTIP_DBUS_NAME "org.freedesktop.Geoclue.Providers.Hostip" +#define HOSTIP_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Hostip" +#define SKYHOOK_DBUS_NAME "org.freedesktop.Geoclue.Providers.Skyhook" +#define SKYHOOK_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Skyhook" +#define UBUNTU_DBUS_NAME "org.freedesktop.Geoclue.Providers.UbuntuGeoIP" +#define UBUNTU_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/UbuntuGeoIP" +#define GEONAMES_DBUS_NAME "org.freedesktop.Geoclue.Providers.Geonames" +#define GEONAMES_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Geonames" +#define PLAZES_DBUS_NAME "org.freedesktop.Geoclue.Providers.Plazes" +#define PLAZES_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Plazes" +#define YAHOO_DBUS_NAME "org.freedesktop.Geoclue.Providers.Yahoo" +#define YAHOO_OBJECT_PATH "/org/freedesktop/Geoclue/Providers/Yahoo" + +/* Master interfaces to control geoclue */ +#define GEOCLUE_MASTER_IFACE "org.freedesktop.Geoclue.Master" +#define GEOCLUE_MASTERCLIENT_IFACE "org.freedesktop.Geoclue.MasterClient" + +/* Provider interfaces to access location data */ +#define GEOCLUE_GEOCLUE_IFACE "org.freedesktop.Geoclue" +#define GEOCLUE_POSITION_IFACE "org.freedesktop.Geoclue.Position" +#define GEOCLUE_ADDRESS_IFACE "org.freedesktop.Geoclue.Address" +#define GEOCLUE_VELOCITY_IFACE "org.freedesktop.Geoclue.Velocity" +#define GEOCLUE_GEOCODE_IFACE "org.freedesktop.Geoclue.Geocode" +#define GEOCLUE_REVERSEGEOCODE_IFACE "org.freedesktop.Geoclue.ReverseGeocode" + +/* More provider interfaces. These three are not in upstream geoclue but only + * in the Tizen version. Lets hope they get upstream at some point. Right now + * we will test at runtime if they are offered and ignore them if not */ +#define GEOCLUE_NMEA_IFACE "org.freedesktop.Geoclue.Nmea" +#define GEOCLUE_SATELLITE_IFACE "org.freedesktop.Geoclue.Satellite" +#define GEOCLUE_POI_IFACE "org.freedesktop.Geoclue.Poi" + +#define GEOCLUE_ADDRESS_KEY_AREA "area" +#define GEOCLUE_ADDRESS_KEY_COUNTRY "country" +#define GEOCLUE_ADDRESS_KEY_COUNTRYCODE "countrycode" +#define GEOCLUE_ADDRESS_KEY_LOCALITY "locality" +#define GEOCLUE_ADDRESS_KEY_POSTALCODE "postalcode" +#define GEOCLUE_ADDRESS_KEY_REGION "region" +#define GEOCLUE_ADDRESS_KEY_STREET "street" + +extern int ELOCATION_EVENT_IN; +extern int ELOCATION_EVENT_OUT; + +/* Some ENUMs that we mimic from GeoClue code as we only access it over the DBus + * interface and share no header file for such defines. + */ + +/** + * @ingroup Location + * @typedef GeocluePositionFields + * @since 1.8 + * + * Bitmask to indicate which of the supplied positions fields are valid. + * + * @{ + */ +typedef enum { + GEOCLUE_POSITION_FIELDS_NONE = 0, + GEOCLUE_POSITION_FIELDS_LATITUDE = 1 << 0, + GEOCLUE_POSITION_FIELDS_LONGITUDE = 1 << 1, + GEOCLUE_POSITION_FIELDS_ALTITUDE = 1 << 2 +} GeocluePositionFields; +/**@}*/ + +/** + * @ingroup Location + * @typedef GeoclueNetworkStatus + * @since 1.8 + * + * Status of the network connectivity for GeoClue. Needed for all providers that + * access external data to determine the location. For example GeoIP or GeoCode + * providers. + * + * @{ + */ +typedef enum { + GEOCLUE_CONNECTIVITY_UNKNOWN, + GEOCLUE_CONNECTIVITY_OFFLINE, + GEOCLUE_CONNECTIVITY_ACQUIRING, + GEOCLUE_CONNECTIVITY_ONLINE, +} GeoclueNetworkStatus; +/**@}*/ + +/** + * @ingroup Location + * @typedef GeoclueStatus + * @since 1.8 + * + * Status of a GeoClue provider. + * + * @{ + */ +typedef enum { + GEOCLUE_STATUS_ERROR, + GEOCLUE_STATUS_UNAVAILABLE, + GEOCLUE_STATUS_ACQUIRING, + GEOCLUE_STATUS_AVAILABLE +} GeoclueStatus; +/**@}*/ + +/** + * @ingroup Location + * @typedef GeoclueVelocityFields + * @since 1.8 + * + * Bitmask to indicate which of the supplied velocity fields are valid. + * + * @{ + */ +typedef enum { + GEOCLUE_VELOCITY_FIELDS_NONE = 0, + GEOCLUE_VELOCITY_FIELDS_SPEED = 1 << 0, + GEOCLUE_VELOCITY_FIELDS_DIRECTION = 1 << 1, + GEOCLUE_VELOCITY_FIELDS_CLIMB = 1 << 2 +} GeoclueVelocityFields; +/**@}*/ + +/** + * @ingroup Location + * @typedef Elocation_Provider + * @since 1.8 + * + * Data structure to hold information about a GeoClue provider. + * + */ +typedef struct _Elocation_Provider +{ + char *name; + char *description; + char *service; + char *path; + GeoclueStatus status; +} Elocation_Provider; +#endif