From a2d2cdaf9f258ec4c6c7c33d912bbe41625e8532 Mon Sep 17 00:00:00 2001 From: Stefan Schmidt Date: Tue, 28 Oct 2014 11:22:55 +0100 Subject: [PATCH] elocation: Add elocation libraray to EFL. 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 GeoClue1 DBus service. Supporting the new GeoClue2 DBus service is planned and worked on. GeoClue offers providers for various techniques to get hold off the current position. Ranging from GeoIP over wifi and GSM cell location to GPS. This has been developed a while ago and was living in my private dev space. It is about time to move this into EFL and bring it forward. The detection of the GeoClue service is being handled on runtime and no new dependency is added due to this library. @feature --- configure.ac | 37 + pc/elocation.pc.in | 12 + src/Makefile.am | 2 +- src/Makefile_Elocation.am | 16 + src/lib/elocation/Elocation.h | 418 ++++++++ src/lib/elocation/elocation.c | 1416 +++++++++++++++++++++++++ src/lib/elocation/elocation_private.h | 181 ++++ 7 files changed, 2081 insertions(+), 1 deletion(-) create mode 100644 pc/elocation.pc.in create mode 100644 src/Makefile_Elocation.am create mode 100644 src/lib/elocation/Elocation.h create mode 100644 src/lib/elocation/elocation.c create mode 100644 src/lib/elocation/elocation_private.h 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