[SF#916] Fix for weather plugin stopped working.
authorAndriy Grytsenko <andrej@rep.kiev.ua>
Thu, 21 Feb 2019 22:47:38 +0000 (00:47 +0200)
committerAndriy Grytsenko <andrej@rep.kiev.ua>
Thu, 21 Feb 2019 22:57:03 +0000 (00:57 +0200)
Add possibility to use few weather providers for weather widget.
Disable Yahoo! Weather because they removed free API support.
Add OpenWeatherMap weather provider.

19 files changed:
ChangeLog
configure.ac
plugins/Makefile.am
plugins/weather/forecast.c
plugins/weather/forecast.h
plugins/weather/httputil.c
plugins/weather/httputil.h
plugins/weather/location.c
plugins/weather/location.h
plugins/weather/openweathermap.c [new file with mode: 0644]
plugins/weather/openweathermap.h [new file with mode: 0644]
plugins/weather/providers.h [new file with mode: 0644]
plugins/weather/weather.c
plugins/weather/weatherwidget.c
plugins/weather/weatherwidget.h
plugins/weather/yahooutil.c
plugins/weather/yahooutil.h
po/POTFILES.in
po/lxpanel.pot

index 9e95b45..5dcebe8 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,9 @@
 * Fixed crash after color was removed from monitor plugin configuration.
 * Aligned used memory calculation in lxpanel monitor with 'free' command line
     utility.
+* Added possibility to use few weather providers for weather widget.
+* Disabled Yahoo! Weather because they removed free API support.
+* Added OpenWeatherMap weather provider.
 
 0.9.3
 -------------------------------------------------------------------------
index 4a0e833..ce18c55 100644 (file)
@@ -270,11 +270,21 @@ if test x"$compile_alsa" = "xno"; then
     fi
 else
     dnl use $plugin_volumealsa for backward compatibility
-    if test -n $plugin_volumealsa; then
+    if test -n "$plugin_volumealsa"; then
         plugin_volume=volume.la
     fi
 fi
 
+if test -n "$plugin_weather"; then
+    curl-config --help &>/dev/null || {
+        AC_MSG_ERROR([weather plugin requires libcurl development files])
+    }
+    CURL_CFLAGS=$(curl-config --cflags)
+    CURL_LIBS=$(curl-config --libs)
+    AC_SUBST(CURL_CFLAGS)
+    AC_SUBST(CURL_LIBS)
+fi
+
 dnl Exclude indicator support when there is no support.
 if test x"$indicator_support" = "xno"; then
     plugin_indicator=
index 497dbea..ab4287e 100644 (file)
@@ -141,15 +141,18 @@ endif
 weather_la_SOURCES = \
        weather/logutil.c          \
        weather/httputil.c         \
-       weather/yahooutil.c        \
+       weather/openweathermap.c   \
        weather/location.c         \
        weather/forecast.c         \
        weather/weatherwidget.c    \
        weather/weather.c
 weather_la_CFLAGS = \
        -I$(srcdir)/weather \
-       $(LIBXML2_CFLAGS)
-weather_la_LIBADD = $(LIBXML2_LIBS)
+       $(LIBXML2_CFLAGS) \
+       $(CURL_CFLAGS)
+weather_la_LIBADD = \
+       $(LIBXML2_LIBS) \
+       $(CURL_LIBS)
 
 # xkb
 xkb_la_CFLAGS = \
@@ -315,7 +318,9 @@ EXTRA_DIST = \
        netstatus/netstatus-util.h \
        weather/logutil.h \
        weather/httputil.h \
+       weather/yahooutil.c \
        weather/yahooutil.h \
+       weather/openweathermap.h \
        weather/location.h \
        weather/forecast.h \
        weather/weatherwidget.h \
index 9990e41..e04476a 100644 (file)
@@ -66,15 +66,13 @@ freeForecastUnits(ForecastUnits * pEntry)
  *
  */
 void
-freeForecast(gpointer pData)
+freeForecast(ForecastInfo * pEntry)
 {
-  if (!pData)
+  if (!pEntry)
     {
       return;
     }
 
-  ForecastInfo * pEntry = (ForecastInfo *)pData;
-
   freeForecastUnits(&pEntry->units_);
 
   freeForecastForecast(&pEntry->today_);
@@ -98,7 +96,7 @@ freeForecast(gpointer pData)
       g_object_unref(pEntry->pImage_);
     }
 
-  g_free(pData);
+  g_free(pEntry);
 }
 
 /**
@@ -108,18 +106,16 @@ freeForecast(gpointer pData)
  *
  */
 void
-printForecast(gpointer pEntry G_GNUC_UNUSED)
+printForecast(ForecastInfo * pInfo G_GNUC_UNUSED)
 {
 #ifdef DEBUG
-  if (!pEntry)
+  if (!pInfo)
     {
       LXW_LOG(LXW_ERROR, "forecast::printForecast(): Entry: NULL");
       
       return;
     }
   
-  ForecastInfo * pInfo = (ForecastInfo *)pEntry;
-  
   LXW_LOG(LXW_VERBOSE, "Forecast at %s:", (const char *)pInfo->pcTime_);
   LXW_LOG(LXW_VERBOSE, "\tTemperature: %d%s", 
           pInfo->iTemperature_,
index f6476f3..b2c48a5 100644 (file)
 #include <glib.h>
 #include <gtk/gtk.h>
 
-#define WIND_DIRECTION(x) ( \
-  ((x>=350 && x<=360) || (x>=0 && x<=11 ))?"N": \
-  (x>11   && x<=33 )?"NNE": \
-  (x>33   && x<=57 )?"NE":  \
-  (x>57   && x<=79 )?"ENE": \
-  (x>79   && x<=101)?"E":   \
-  (x>101  && x<=123)?"ESE": \
-  (x>123  && x<=147)?"SE":  \
-  (x>147  && x<=169)?"SSE": \
-  (x>169  && x<=192)?"S":   \
-  (x>192  && x<=214)?"SSW": \
-  (x>214  && x<=236)?"SW":  \
-  (x>236  && x<=258)?"WSW": \
-  (x>258  && x<=282)?"W":   \
-  (x>282  && x<=304)?"WNW": \
-  (x>304  && x<=326)?"NW":  \
-  (x>326  && x<=349)?"NNW":"")
-
 typedef enum
 {
   STEADY, // 0
@@ -96,7 +78,7 @@ typedef struct
  *
  */
 void
-freeForecast(gpointer pData);
+freeForecast(ForecastInfo * pData);
 
 /**
  * Prints the contents of the supplied entry to stdout
@@ -105,6 +87,6 @@ freeForecast(gpointer pData);
  *
  */
 void
-printForecast(gpointer pEntry);
+printForecast(ForecastInfo * pEntry);
 
 #endif
index 38cd694..fc91470 100644 (file)
@@ -1,5 +1,6 @@
 /**
  * Copyright (c) 2012-2014 Piotr Sipika; see the AUTHORS file for more.
+ * Copyright (c) 2019 Andriy Grytsenko <andrej@rep.kiev.ua>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 #include "httputil.h"
 
-#include <libxml/nanohttp.h>
-#include <libxml/xmlmemory.h>
-
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
-/**
- * Cleans up the nano HTTP state
- *
- * @param pContext HTTP Context
- * @param pContentType Content-type container
- */
-static void
-cleanup(void * pContext, char * pContentType)
-{
-  if (pContext)
-    {
-      xmlNanoHTTPClose(pContext);
-    }
+struct wdata_t {
+    char *buff;
+    size_t alloc;
+};
 
-  if (pContentType)
-    {
-      xmlFree(pContentType);
-    }
-
-  xmlNanoHTTPCleanup();
+static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+    struct wdata_t *data = userp;
+    size_t todo = size * nmemb;
+    size_t new_alloc = data->alloc + todo;
+
+    if (todo == 0)
+        return 0;
+    data->buff = realloc(data->buff, new_alloc + 1);
+    if (data->buff == NULL)
+        return 0; /* is that correct? */
+    memcpy(&data->buff[data->alloc], buffer, todo);
+    data->alloc = new_alloc;
+    return todo;
 }
 
 /**
@@ -59,107 +58,44 @@ cleanup(void * pContext, char * pContentType)
  * @return A pointer to a null-terminated buffer containing the textual 
  *         representation of the response. Must be freed by the caller.
  */
-gpointer
-getURL(const gchar * pczURL, gint * piRetCode, gint * piDataSize)
+CURLcode
+getURL(const gchar * pczURL, gchar ** pcData, gint * piDataSize, const gchar ** pccHeaders)
 {
-  /* nanohttp magic */
-#define iBufReadSize 1024
-  gint iReadSize = 0;
-  gint iCurrSize = 0;
+    struct curl_slist *headers=NULL;
+    CURL *curl;
+    CURLcode res;
+    struct wdata_t data = { NULL, 0 };
 
-  gpointer pInBuffer = NULL;
-  gpointer pInBufferRef = NULL;
+    if (!pczURL)
+        return CURLE_URL_MALFORMAT;
 
-  gchar cReadBuffer[iBufReadSize];
-  bzero(cReadBuffer, iBufReadSize);
-
-  xmlNanoHTTPInit();
-
-  char * pContentType = NULL;
-  void * pHTTPContext = NULL;
-
-  pHTTPContext = xmlNanoHTTPOpen(pczURL, &pContentType);
-
-  if (!pHTTPContext)
+    if (pccHeaders)
     {
-      // failure
-      cleanup(pHTTPContext, pContentType);
-
-      *piRetCode = -1;
-
-      return pInBuffer; // it's NULL
-    }
-
-  *piRetCode = xmlNanoHTTPReturnCode(pHTTPContext);
-
-  if (*piRetCode != HTTP_STATUS_OK)
-    {
-      // failure
-      cleanup(pHTTPContext, pContentType);
-
-      return pInBuffer; // it's NULL
+        while (*pccHeaders)
+            headers = curl_slist_append(headers, *pccHeaders++);
     }
-
-  while ((iReadSize = xmlNanoHTTPRead(pHTTPContext, cReadBuffer, iBufReadSize)) > 0)
-    {
-      // set return code
-      *piRetCode = xmlNanoHTTPReturnCode(pHTTPContext);
-
-      /* Maintain pointer to old location, free on failure */
-      pInBufferRef = pInBuffer;
-
-      pInBuffer = g_try_realloc(pInBuffer, iCurrSize + iReadSize);
-
-      if (!pInBuffer || *piRetCode != HTTP_STATUS_OK)
-        {
-          // failure
-          cleanup(pHTTPContext, pContentType);
-
-          g_free(pInBufferRef);
-
-          return pInBuffer; // it's NULL
-        }
-
-      memcpy(pInBuffer + iCurrSize, cReadBuffer, iReadSize);
-      
-      iCurrSize += iReadSize;
-
-      // clear read buffer
-      bzero(cReadBuffer, iBufReadSize);
-
-      *piDataSize = iCurrSize;
-    }
-
-  if (iReadSize < 0)
-    {
-      // error
-      g_free(pInBuffer);
-
-      pInBuffer = NULL;
-    }
-  else
-    {
-      /* Maintain pointer to old location, free on failure */
-      pInBufferRef = pInBuffer;
-
-      // need to add '\0' at the end
-      pInBuffer = g_try_realloc(pInBuffer, iCurrSize + 1);
-
-      if (!pInBuffer)
-        {
-          // failure
-          g_free(pInBufferRef);
-
-          pInBuffer = NULL;
-        }
-      else
-        {
-          memcpy(pInBuffer + iCurrSize, "\0", 1);
-        }
-    }
-  
-  // finish up
-  cleanup(pHTTPContext, pContentType);
-
-  return pInBuffer;
+    curl_global_init(CURL_GLOBAL_SSL);
+    curl = curl_easy_init();
+    curl_easy_setopt(curl, CURLOPT_URL, pczURL);
+    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
+    res = curl_easy_perform(curl);
+    if (data.buff)
+        data.buff[data.alloc] = '\0';
+
+    if (pcData)
+        *pcData = data.buff;
+    else
+        g_free(data.buff);
+    if (piDataSize)
+        *piDataSize = data.alloc;
+
+    //if (res != CURLE_OK)
+      //fprintf(stderr, "curl_easy_perform() failed: %s\n",
+              //curl_easy_strerror(res));
+
+    curl_slist_free_all(headers);
+    curl_easy_cleanup(curl);
+    return res;
 }
index 472bdd0..3fb4122 100644 (file)
 #define LXWEATHER_HTTPUTIL_HEADER
 
 #include <glib.h>
-
-static const gint HTTP_STATUS_OK = 200;
+#include <curl/curl.h>
 
 /**
  * Returns the contents of the requested URL
  *
  * @param pczURL The URL to retrieve [in].
- * @param piRetCode The return code supplied with the response [out].
+ * @param pcData A pointer to a null-terminated buffer containing the textual
+ *         representation of the response. Must be freed by the caller. [out].
  * @param piDataSize The resulting data length [out].
+ * @param headers Extra headers for GET request [in].
  *
- * @return A pointer to a null-terminated buffer containing the textual
- *         representation of the response. Must be freed by the caller.
+ * @return The return code supplied by CURL
  */
-gpointer
-getURL(const gchar * pczURL, gint * piRetCode, gint * piDataSize);
+CURLcode
+getURL(const gchar * pczURL, gchar ** pcData, gint * piDataSize, const gchar ** headers);
 
 #endif
index b8e1cbb..7872b55 100644 (file)
@@ -44,22 +44,20 @@ const gchar * LocationInfoFieldNames[] = { "alias",
  *
  */
 void
-freeLocation(gpointer pData)
+freeLocation(LocationInfo * pEntry)
 {
-  if (!pData)
+  if (!pEntry)
     {
       return;
     }
 
-  LocationInfo * pEntry = (LocationInfo *)pData;
-
   g_free(pEntry->pcAlias_);
   g_free(pEntry->pcCity_);
   g_free(pEntry->pcState_);
   g_free(pEntry->pcCountry_);
   g_free(pEntry->pcWOEID_);
 
-  g_free(pData);
+  g_free(pEntry);
 }
 
 /**
@@ -69,18 +67,16 @@ freeLocation(gpointer pData)
  *
  */
 void
-printLocation(gpointer pEntry G_GNUC_UNUSED)
+printLocation(LocationInfo * pInfo G_GNUC_UNUSED)
 {
 #ifdef DEBUG
-  if (!pEntry)
+  if (!pInfo)
     {
       LXW_LOG(LXW_ERROR, "location::printLocation(): Entry: NULL");
       
       return;
     }
 
-  LocationInfo * pInfo = (LocationInfo *)pEntry;
-
   LXW_LOG(LXW_VERBOSE, "Entry:");
   LXW_LOG(LXW_VERBOSE, "\tAlias: %s", (const char *)pInfo->pcAlias_);
   LXW_LOG(LXW_VERBOSE, "\tCity: %s", (const char *)pInfo->pcCity_);
@@ -102,19 +98,15 @@ printLocation(gpointer pEntry G_GNUC_UNUSED)
  *
  */
 void
-setLocationAlias(gpointer pEntry, gpointer pData)
+setLocationAlias(LocationInfo * pLocation, const gchar * pczAlias)
 {
-  if (!pEntry)
+  if (!pLocation)
     {
       LXW_LOG(LXW_ERROR, "Location: NULL");
 
       return;
     }
 
-  LocationInfo * pLocation = (LocationInfo *)pEntry;
-
-  const gchar * pczAlias = (const gchar *)pData;
-
   gsize aliasLength = (pczAlias)?strlen(pczAlias):0;
 
   if (pLocation->pcAlias_)
@@ -136,25 +128,23 @@ setLocationAlias(gpointer pEntry, gpointer pData)
  *       the caller.
  */
 void
-copyLocation(gpointer * pDestination, gpointer pSource)
+copyLocation(LocationInfo ** pDestination, LocationInfo * pSource)
 {
   if (!pSource || !pDestination)
     {
       return;
     }
 
-  if ((LocationInfo *)*pDestination)
+  if (*pDestination)
     {
       /* Check if the two are the same, first */
-      LocationInfo * pDstLocation = (LocationInfo *) *pDestination;
-
-      LocationInfo * pSrcLocation = (LocationInfo *)pSource;
+      LocationInfo * pDstLocation = *pDestination;
 
-      if (!strncmp(pDstLocation->pcWOEID_, pSrcLocation->pcWOEID_, strlen(pSrcLocation->pcWOEID_)))
+      if (pSource->pcWOEID_ && !g_strcmp0(pDstLocation->pcWOEID_, pSource->pcWOEID_))
         {
           /* they're the same, no need to copy, just assign alias */
-          setLocationAlias(*pDestination, pSrcLocation->pcAlias_);
-          
+          setLocationAlias(*pDestination, pSource->pcAlias_);
+
           return;
         }
 
@@ -188,6 +178,10 @@ copyLocation(gpointer * pDestination, gpointer pSource)
 
       pDest->cUnits_ = (pSrc->cUnits_) ? pSrc->cUnits_ : 'f';
 
+      pDest->dLongitude_ = pSrc->dLongitude_;
+
+      pDest->dLatitude_ = pSrc->dLatitude_;
+
       pDest->uiInterval_ = pSrc->uiInterval_;
 
       pDest->bEnabled_ = pSrc->bEnabled_;
index 58f062d..24dc8c4 100644 (file)
@@ -38,6 +38,8 @@ typedef struct
   gchar * pcState_;
   gchar * pcCountry_;
   gchar * pcWOEID_;
+  gdouble dLatitude_;
+  gdouble dLongitude_;
   gchar cUnits_;
   guint uiInterval_;
   gboolean bEnabled_;
@@ -68,7 +70,7 @@ extern const gchar * LocationInfoFieldNames[];
  *
  */
 void
-freeLocation(gpointer pData);
+freeLocation(LocationInfo * pData);
 
 /**
  * Prints the contents of the supplied entry to stdout
@@ -77,7 +79,7 @@ freeLocation(gpointer pData);
  *
  */
 void
-printLocation(gpointer pEntry);
+printLocation(LocationInfo * pEntry);
 
 /**
  * Sets the alias for the location
@@ -87,7 +89,7 @@ printLocation(gpointer pEntry);
  *
  */
 void
-setLocationAlias(gpointer pEntry, gpointer pData);
+setLocationAlias(LocationInfo * pEntry, const gchar * pData);
 
 /**
  * Copies a location entry.
@@ -100,6 +102,6 @@ setLocationAlias(gpointer pEntry, gpointer pData);
  *       the caller.
  */
 void
-copyLocation(gpointer * pDestination, gpointer pSource);
+copyLocation(LocationInfo ** pDestination, LocationInfo * pSource);
 
 #endif
diff --git a/plugins/weather/openweathermap.c b/plugins/weather/openweathermap.c
new file mode 100644 (file)
index 0000000..c6b4e78
--- /dev/null
@@ -0,0 +1,922 @@
+/*
+ * Copyright (C) 2012-2014 Piotr Sipika.
+ * Copyright (C) 2019 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * See the COPYRIGHT file for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "httputil.h"
+#include "location.h"
+#include "forecast.h"
+#include "logutil.h"
+#include "openweathermap.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xmlstring.h>
+#include <libxml/uri.h>
+#include <libxml/xpathInternals.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <time.h>
+#include <sys/utsname.h>
+
+#define XMLCHAR_P(x) (xmlChar *)(x)
+#define CONSTXMLCHAR_P(x) (const xmlChar *)(x)
+#define CONSTCHAR_P(x) (const char *)(x)
+#define CHAR_P(x) (char *)(x)
+
+static gint g_iInitialized = 0;
+
+struct ProviderInfo {
+    char *wLang;
+};
+
+
+/**
+ * Generates the forecast query string
+ *
+ * @param cQuery Buffer to contain the query
+ * @param pczWOEID WOEID string
+ * @param czUnits Units character (length of 1)
+ *
+ * @return 0 on success, -1 on failure
+ */
+static gchar *
+getForecastQuery(gdouble latitude, gdouble longitude, const gchar czUnits, const gchar *lang)
+{
+    return g_strdup_printf("http://api.openweathermap.org/data/2.5/weather?"
+                           "lat=%.4f&lon=%.4f&APPID=" WEATHER_APPID "&mode=xml&"
+                           "units=%s%s%s", latitude, longitude,
+                           czUnits == 'c' ? "metric" : "imperial",
+                           lang ? "&lang=" : "", lang ? lang : "");
+}
+
+/**
+ * Converts the passed-in string from UTF-8 to ASCII for http transmisison.
+ *
+ * @param pczInString String to convert
+ *
+ * @return The converted string which MUST BE FREED BY THE CALLER.
+ */
+static gchar *
+convertToASCII(const gchar *pczInString)
+{
+  // for UTF-8 to ASCII conversions
+  setlocale(LC_CTYPE, "en_US");
+
+  GError * pError = NULL;
+
+  gsize szBytesRead = 0;
+  gsize szBytesWritten = 0;
+
+  gchar * pcConvertedString = g_convert(pczInString,
+                                        strlen(pczInString),
+                                        "ASCII//TRANSLIT",
+                                        "UTF-8",
+                                        &szBytesRead,
+                                        &szBytesWritten,
+                                        &pError);
+
+  if (pError)
+    {
+      LXW_LOG(LXW_ERROR, "openweathermap::convertToASCII(%s): Error: %s",
+              pczInString, pError->message);
+
+      g_error_free(pError);
+
+      pcConvertedString = g_strndup(pczInString, strlen(pczInString));
+    }
+
+  // now escape space, if any
+  xmlChar * pxEscapedString = xmlURIEscapeStr((const xmlChar *)pcConvertedString, NULL);
+
+  if (pxEscapedString)
+    {
+      // release ConvertedString, reset it, then release EscapedString.
+      // I know it's confusing, but keeps everything as a gchar and g_free
+      g_free(pcConvertedString);
+
+      pcConvertedString = g_strndup((const gchar *)pxEscapedString,
+                                    strlen((const gchar *)pxEscapedString));
+
+      xmlFree(pxEscapedString);
+    }
+
+  // restore locale to default
+  setlocale(LC_CTYPE, "");
+
+  return pcConvertedString;
+}
+
+/**
+ * Compares two strings and then sets the storage variable to the second
+ * value if the two do not match. The storage variable is cleared first.
+ *
+ * @param pcStorage Pointer to the storage location with the first value.
+ * @param pczString2 The second string.
+ * @param szString2 The length of the second string.
+ *
+ * @return 0 on succes, -1 on failure.
+ */
+static gint
+setStringIfDifferent(gchar ** pcStorage,
+                     const gchar * pczString2,
+                     const gsize szString2)
+{
+  // if diffrent, clear and set
+  if (g_strcmp0(*pcStorage, pczString2))
+    {
+      g_free(*pcStorage);
+
+      *pcStorage = g_strndup(pczString2, szString2);
+    }
+
+  return 0;
+}
+
+/**
+ * Compares the URL of an image to the 'new' value. If the two
+ * are different, the image at the 'new' URL is retrieved and replaces
+ * the old one. The old one is freed.
+ *
+ * @param pcStorage Pointer to the storage location with the first value.
+ * @param pImage Pointer to the image storage.
+ * @param pczNewURL The new url.
+ * @param szURLLength The length of the new URL.
+ *
+ * @return 0 on succes, -1 on failure.
+ */
+static gint
+setImageIfDifferent(gchar ** pcStorage,
+                    GdkPixbuf ** pImage,
+                    const gchar * pczNewURL,
+                    const gsize szURLLength)
+{
+  int err = 0;
+
+  // if diffrent, clear and set
+  if (g_strcmp0(*pcStorage, pczNewURL))
+    {
+      g_free(*pcStorage);
+
+      *pcStorage = g_strndup(pczNewURL, szURLLength);
+
+      if (*pImage)
+        {
+          g_object_unref(*pImage);
+
+          *pImage = NULL;
+        }
+
+      // retrieve the URL and create the new image
+      CURLcode iRetCode = 0;
+      gint iDataSize = 0;
+      char * pResponse = NULL;
+
+      iRetCode = getURL(pczNewURL, &pResponse, &iDataSize, NULL);
+
+      if (!pResponse || iRetCode != CURLE_OK)
+        {
+          LXW_LOG(LXW_ERROR, "openweathermap::setImageIfDifferent(): Failed to get URL (%d, %d)",
+                  iRetCode, iDataSize);
+          g_free(pResponse);
+
+          return -1;
+        }
+
+      GInputStream * pInputStream = g_memory_input_stream_new_from_data(pResponse,
+                                                                        iDataSize,
+                                                                        g_free);
+
+      GError * pError = NULL;
+
+      *pImage = gdk_pixbuf_new_from_stream(pInputStream,
+                                           NULL,
+                                           &pError);
+
+      if (!*pImage)
+        {
+          LXW_LOG(LXW_ERROR, "openweathermap::setImageIfDifferent(): PixBuff allocation failed: %s",
+                  pError->message);
+
+          g_error_free(pError);
+
+          err = -1;
+        }
+
+      if (!g_input_stream_close(pInputStream, NULL, &pError))
+        {
+          LXW_LOG(LXW_ERROR, "openweathermap::setImageIfDifferent(): InputStream closure failed: %s",
+                  pError->message);
+
+          g_error_free(pError);
+
+          err = -1;
+        }
+
+    }
+
+  return err;
+}
+
+/**
+ * Compares an integer to a converted string and then sets the storage variable
+ * to the second value if the two do not match.
+ *
+ * @param piStorage Pointer to the storage location with the first value.
+ * @param pczString2 The second string.
+ *
+ * @return 0 on succes, -1 on failure.
+ */
+static gint
+setIntIfDifferent(gint * piStorage, const gchar * pczString2)
+{
+  gint iValue = (gint)g_ascii_strtoll((pczString2)?pczString2:"0", NULL, 10);
+
+  // if diffrent, set
+  if (*piStorage != iValue)
+    {
+      *piStorage = iValue;
+    }
+
+  return 0;
+}
+
+static gint
+setTimeIfDifferent(gchar ** pcStorage, const gchar * pczString)
+{
+  int hour, min, sec;
+  char * setTime = pczString ? strchr(pczString, 'T') : NULL;
+  char adjTime[16];
+
+  if (setTime && sscanf(setTime, "T%2u:%2u:%2u", &hour, &min, &sec) == 3)
+    {
+      sec -= timezone % 60;
+      min -= (timezone / 60) % 60;
+      hour -= timezone / 3600;
+      if (sec < 0)
+        {
+          sec += 60;
+          min--;
+        }
+      min += sec / 60;
+      sec %= 60;
+      if (min < 0)
+        {
+          min += 60;
+          hour--;
+        }
+      hour += min / 60;
+      min %= 60;
+      if (hour < 0)
+        {
+          hour += 24;
+        }
+      hour %= 24;
+      snprintf(adjTime, sizeof(adjTime), "%02d:%02d:%02d", hour, min, sec); // FIXME: AM/PM
+      setTime = adjTime;
+    }
+  else
+   setTime = NULL;
+
+  return setStringIfDifferent(pcStorage, setTime, setTime ? strlen(setTime) : 0);
+}
+
+static void
+processCityNode(ForecastInfo * pEntry, xmlNodePtr pNode)
+{
+    xmlNodePtr pCurr;
+
+    for (pCurr = pNode->children; pCurr; pCurr = pCurr->next)
+    {
+        if (pCurr->type == XML_ELEMENT_NODE)
+        {
+            if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("sun"))) // rise="2019-02-16T05:06:50" set="2019-02-16T15:17:50"
+            {
+                char * rise = CHAR_P(xmlGetProp(pCurr, XMLCHAR_P("rise")));
+                char * set = CHAR_P(xmlGetProp(pCurr, XMLCHAR_P("set")));
+
+                setTimeIfDifferent(&pEntry->pcSunrise_, rise);
+                setTimeIfDifferent(&pEntry->pcSunset_, set);
+                xmlFree(rise);
+                xmlFree(set);
+            }
+        }
+    }
+}
+
+static void
+processWindNode(ForecastInfo * pEntry, xmlNodePtr pNode, const gchar czUnits)
+{
+    xmlNodePtr pCurr;
+
+    for (pCurr = pNode->children; pCurr; pCurr = pCurr->next)
+    {
+        if (pCurr->type == XML_ELEMENT_NODE)
+        {
+            if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("speed"))) // value="5" name="Gentle Breeze"
+            {
+                char * value = CHAR_P(xmlGetProp(pCurr, XMLCHAR_P("value")));
+                const char * units = (czUnits == 'f') ? _("Mph") : _("m/s");
+
+                setIntIfDifferent(&pEntry->iWindSpeed_, value);
+                setStringIfDifferent(&pEntry->units_.pcSpeed_, units, strlen(units));
+                xmlFree(value);
+            }
+            else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("direction"))) // value="270" code="W" name="West"
+            {
+                char * code = CHAR_P(xmlGetProp(pCurr, XMLCHAR_P("code")));
+                const char * name = code ? _(code) : NULL;
+                gsize nlen = (name)?strlen(name):0;
+
+                setStringIfDifferent(&pEntry->pcWindDirection_, name, nlen);
+                xmlFree(code);
+            }
+        }
+    }
+}
+
+/**
+ * Parses the response and fills in the supplied list with entries (if any)
+ *
+ * @param pResponse Pointer to the response received.
+ * @param pList Pointer to the pointer to the list to populate.
+ * @param pForecast Pointer to the pointer to the forecast to retrieve.
+ *
+ * @return 0 on success, -1 on failure
+ *
+ * @note If the pList pointer is NULL or the pForecast pointer is NULL,
+ *       nothing is done and failure is returned. Otherwise, the appropriate
+ *       pointer is set based on the name of the XML element:
+ *       'Result' for GList (pList)
+ *       'channel' for Forecast (pForecast)
+ */
+static gint
+parseResponse(const char * pResponse, GList ** pList, ForecastInfo ** pForecast, const gchar czUnits)
+{
+  xmlDocPtr pDoc = xmlReadMemory(pResponse,
+                                 strlen(pResponse),
+                                 "",
+                                 NULL,
+                                 0);
+
+  if (!pDoc)
+    {
+      // failed
+      LXW_LOG(LXW_ERROR, "openweathermap::parseResponse(): Failed to parse response %s",
+              pResponse);
+
+      return -1;
+    }
+
+  xmlNodePtr pRoot = xmlDocGetRootElement(pDoc);
+
+  // the second part of the if can be broken out
+  if (!pRoot || !xmlStrEqual(pRoot->name, CONSTXMLCHAR_P("current")))
+    {
+      // failed
+      LXW_LOG(LXW_ERROR, "openweathermap::parseResponse(): Failed to retrieve root %s",
+              pResponse);
+
+      xmlFreeDoc(pDoc);
+
+      return -1;
+    }
+
+  // have some results...
+  xmlNodePtr pNode = pRoot->children;
+
+  ForecastInfo * pEntry = NULL;
+
+  if (pForecast)
+    {
+      if (*pForecast)
+        {
+          pEntry = *pForecast;
+        }
+      else
+        {
+          pEntry = (ForecastInfo *)g_try_new0(ForecastInfo, 1);
+        }
+    }
+
+  if (!pEntry)
+    {
+      xmlFreeDoc(pDoc);
+      return -1;
+    }
+
+  for (pNode = pRoot->children; pNode; pNode = pNode->next)
+    {
+      if (pNode && pNode->type == XML_ELEMENT_NODE)
+        {
+          if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("city")))
+            {
+              processCityNode(pEntry, pNode);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("temperature"))) // value="3" min="3" max="3" unit="metric"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+//              char * min = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("min")));
+//              char * max = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("max")));
+              char * unit = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("unit")));
+
+              setIntIfDifferent(&pEntry->iTemperature_, value);
+//              setIntIfDifferent(&pEntry->today_.iLow_, min);
+//              setIntIfDifferent(&pEntry->today_.iHigh_, max);
+              switch (unit[0])
+                {
+                  case 'c': case 'C': /* Celsius */
+                  case 'm': /* metric */
+                    setStringIfDifferent(&pEntry->units_.pcTemperature_, "C", 1);
+                    break;
+                  case 'f': case 'F': /* Fahrengeith */
+                  case 'i': /* imperial */
+                    setStringIfDifferent(&pEntry->units_.pcTemperature_, "F", 1);
+                    break;
+                  default: /* Kelvin */
+                    setStringIfDifferent(&pEntry->units_.pcTemperature_, "K", 1);
+                    break;
+                }
+              xmlFree(value);
+//              xmlFree(min);
+//              xmlFree(max);
+              xmlFree(unit);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("humidity"))) // value="93" unit="%"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+
+              setIntIfDifferent(&pEntry->iHumidity_, value);
+              xmlFree(value);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("pressure"))) // value="1022" unit="hPa"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+              char * unit = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("unit")));
+              gsize ulen = (value)?strlen(value):0;
+
+              pEntry->dPressure_ = g_strtod((value)?value:"0", NULL);
+              setStringIfDifferent(&pEntry->units_.pcPressure_, unit, ulen);
+              xmlFree(value);
+              xmlFree(unit);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("wind")))
+            {
+              processWindNode(pEntry, pNode, czUnits);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("clouds"))) // value="40" name="scattered clouds"
+            {
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("visibility"))) // value="7000"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+              const char * units = _("m");
+
+              pEntry->dVisibility_ = g_strtod((value)?value:"0", NULL);
+              setStringIfDifferent(&pEntry->units_.pcDistance_, units, strlen(units));
+              xmlFree(value);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("precipitation"))) // mode="no" // value="0.025" mode="rain"
+            {
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("weather"))) // number="701" value="mist" icon="50n"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+              char * icon = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("icon")));
+              char * pcImageURL = NULL;
+              gsize vlen = (value)?strlen(value):0;
+
+              setStringIfDifferent(&pEntry->pcConditions_, value, vlen);
+              if (icon)
+                pcImageURL = g_strdup_printf("http://openweathermap.org/img/w/%s.png", icon);
+              setImageIfDifferent(&pEntry->pcImageURL_,
+                                  &pEntry->pImage_,
+                                  pcImageURL,
+                                  strlen(pcImageURL));
+
+              xmlFree(value);
+              xmlFree(icon);
+              g_free(pcImageURL);
+            }
+          else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("lastupdate"))) // value="2019-02-16T18:00:00"
+            {
+              char * value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("value")));
+
+              setTimeIfDifferent(&pEntry->pcTime_, value);
+              //FIXME: count timezone
+              xmlFree(value);
+            }
+        }// end if element
+    }// end for pRoot children
+
+  *pForecast = pEntry;
+
+  xmlFreeDoc(pDoc);
+
+  return 0;
+}
+
+/**
+ * pairs: first is ISO code, second is code on site
+ */
+static const char *localeTranslations[] = {
+    "cs", "cz", /* Checz */
+    "ko", "kr", /* Korean */
+    "lv", "la", /* Latvian */
+    "sv", "se", /* Swedish */
+    "uk", "ua", /* Ukrainian */
+    "zh_CN", "zh_cn",
+    "zh_TW", "zh_tw",
+    NULL
+};
+
+/**
+ * Initializes the internals: XML
+ *
+ */
+static ProviderInfo *initOWM(void)
+{
+    ProviderInfo *info = g_malloc(sizeof(ProviderInfo));
+
+    if (!info)
+        /* out of memory! */
+        return info;
+
+    if (!g_iInitialized)
+    {
+        xmlInitParser();
+        g_iInitialized = 1;
+    }
+
+    const char *locale = setlocale(LC_MESSAGES, NULL); /* query locale */
+    const char **localeTranslation = localeTranslations;
+
+    tzset();
+    info->wLang = g_strndup(locale, 2);
+    if (locale)
+    {
+        for (; *localeTranslation; localeTranslation += 2)
+        {
+            if (strncmp(localeTranslation[0], locale,
+                        strlen(localeTranslation[0])) == 0)
+            {
+                g_free(info->wLang);
+                info->wLang = g_strdup(localeTranslation[1]);
+                break;
+            }
+        }
+    }
+
+    //g_debug("%s: %p",__func__,info);
+    return info;
+}
+
+/**
+ * Cleans up the internals: XML
+ *
+ */
+static void freeOWM(ProviderInfo *instance)
+{
+    //g_debug("%s: %p",__func__,instance);
+    g_free(instance->wLang);
+    g_free(instance);
+}
+
+static int processOSMPlace(LocationInfo *info, xmlNodePtr pNode)
+{
+/*
+type=".....":
+display_name="Дуда, Харгита, 537302, Румунія" 1 2 4
+display_name="Дуда, Рытанский сельский Совет, Островецький район, Гродненська область, Білорусь" 1 2+ 5
+display_name="Duda, Bali, Індонезія" 1 2 3
+display_name="Dudar, Muzaffargarh District, Пенджаб, Пакистан" 1 2+ 3
+display_name="Дударків, Бориспільський район, Київська область, 08330, Україна" 1 2+ 5
+display_name="Berlin, Hartford County, Коннектикут, 06037, Сполучені Штати Америки" 1 2+ 5
+type="city":
+display_name="Київ, Шевченківський район, Київ, 1001, Україна" 1 3 5
+display_name="Київ, Україна" 1 - 2
+display_name="Житомир, Житомирська міська територіальна громада, Житомирська область, 10000-10499, Україна" 1 3 5
+display_name="Житомир, Житомирська міська територіальна громада, Житомирська область, Україна" 1 3 4
+display_name="Берлін, 10117, Німеччина" 1 - 3
+display_name="Berlin, Coös County, Нью-Гемпшир, 03570, Сполучені Штати Америки" 1 3 5
+display_name="City of Berlin, Green Lake County, Вісконсин, Сполучені Штати Америки" 1 3 4
+ */
+    char *value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("class")));
+    char *name, *reg, *ptr;
+    int res;
+    gboolean is_city;
+
+    if (!value) /* no class property */
+        goto _fail;
+
+    res = strcmp(value, "place");
+    xmlFree(value);
+    if (res != 0) /* ignore other than class="place" */
+        goto _fail;
+
+    value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("type")));
+    is_city = g_strcmp0(value, "city") == 0;
+    xmlFree(value);
+
+    value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("lon")));
+    if (!value) /* no longitude */
+        goto _fail;
+    info->dLongitude_ = g_strtod(value, NULL);
+    xmlFree(value);
+
+    value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("lat")));
+    if (!value) /* no latitude */
+        goto _fail;
+    info->dLatitude_ = g_strtod(value, NULL);
+    xmlFree(value);
+
+    value = CHAR_P(xmlGetProp(pNode, XMLCHAR_P("display_name")));
+    if (!value) /* no name - is that possible? */
+        goto _fail;
+    name = g_strdup(value);
+    xmlFree(value);
+    if (!name) /* memory error? */
+        goto _fail;
+    reg = strchr(name, ',');
+    if (reg) /* isolate first word as city */
+    {
+        *reg++ = '\0';
+        while (*reg == ' ') reg++;
+        info->pcCity_ = g_strdup(name);
+        ptr = strrchr(reg, ',');
+        if (ptr)
+        {
+            *ptr++ = '\0';
+            while (*ptr == ' ') ptr++;
+            info->pcCountry_ = g_strdup(ptr);
+            ptr = strrchr(reg, ',');
+            if (ptr)
+            {
+                if (strtol(ptr + 1, NULL, 10) > 0) /* skip 2nd last if it's numeric */
+                    *ptr++ = '\0';
+                while (*ptr == ' ') ptr++;
+                if (is_city)
+                {
+                    /* skip 2nd from region if type="city" */
+                    ptr = strchr(reg, ',');
+                    if (ptr)
+                    {
+                        reg = ptr + 1;
+                        while (*reg == ' ') reg++;
+                    }
+                }
+                info->pcState_ = g_strdup(reg);
+            }
+            else if (strtoul(reg, &ptr, 10) == 0 && ptr == reg)
+                info->pcState_ = g_strdup(reg);
+            /* otherwise it's "City, ZIP, Country" */
+        }
+        else /* if name consists of two tokens then it's "City, Country" */
+            info->pcCountry_ = g_strdup(reg);
+    }
+    else /* if name consists of a single token then it's a small country */
+        info->pcCountry_ = g_strdup(name);
+    g_free(name);
+
+    return 1;
+
+_fail:
+    freeLocation(info);
+    return 0;
+}
+
+static GList *parseOSMResponse(const gchar *pResponse, const gchar *locale)
+{
+/*
+<searchresults timestamp="Sun, 17 Feb 19 01:59:27 +0000" attribution="Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright" querystring="Дударків" polygon="false" exclude_place_ids="1537043" more_url="https://nominatim.openstreetmap.org/search.php?q=%D0%94%D1%83%D0%B4%D0%B0%D1%80%D0%BA%D1%96%D0%B2&exclude_place_ids=1537043&format=xml&accept-language=uk%2Cen%3Bq%3D0.9%2Cen-US%3Bq%3D0.8%2Cru%3Bq%3D0.7">
+<place place_id="1537043" osm_type="node" osm_id="337521620" place_rank="19" boundingbox="50.429219,50.469219,30.93158,30.97158" lat="50.449219" lon="30.95158" display_name="Дударків, Бориспільський район, Київська область, 08330, Україна" class="place" type="village" importance="0.43621598500338" icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_village.p.20.png"/>
+<place place_id="240722518" osm_type="relation" osm_id="8759567" place_rank="19" boundingbox="47.8622784,47.8705346,31.012428,31.024226" lat="47.8671228" lon="31.0179572" display_name="Київ, Доманівський район, Миколаївська область, Україна" class="place" type="hamlet" importance="0.275" icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_village.p.20.png"/>
+<place place_id="127538" osm_type="node" osm_id="26150422" place_rank="15" boundingbox="50.2900644,50.6100644,30.3641037,30.6841037" lat="50.4500644" lon="30.5241037" display_name="Київ, Шевченківський район, Київ, 1001, Україна" class="place" type="city" importance="0.74145054816511" icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png"/>
+<place place_id="197890553" osm_type="relation" osm_id="421866" place_rank="16" boundingbox="50.2132422,50.590833,30.2363911,30.8276549" lat="50.4020865" lon="30.6146803128848" display_name="Київ, Україна" class="place" type="city" importance="0.74145054816511" icon="https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png"/>
+</searchresults>
+ */
+    GList *list = NULL;
+    xmlDocPtr pDoc = xmlReadMemory(pResponse, strlen(pResponse), "", NULL, 0);
+    xmlNodePtr pRoot;
+    xmlNodePtr pNode;
+    char cUnits;
+
+    if (!pDoc)
+    {
+        // failed
+        LXW_LOG(LXW_ERROR, "openweathermap::parseOSMResponse(): Failed to parse response %s",
+                pResponse);
+
+        return NULL;
+    }
+
+    pRoot = xmlDocGetRootElement(pDoc);
+
+    // the second part of the if can be broken out
+    if (!pRoot || !xmlStrEqual(pRoot->name, CONSTXMLCHAR_P("searchresults")))
+    {
+        // failed
+        LXW_LOG(LXW_ERROR, "openweathermap::parseResponse(): Failed to retrieve root %s",
+                pResponse);
+
+        xmlFreeDoc(pDoc);
+
+        return NULL;
+    }
+
+    /* guess units by locale */
+    if (strncmp(locale, "en", 2) == 0 || strncmp(locale, "my", 2) == 0)
+        cUnits = 'f';
+    else
+        cUnits = 'c';
+
+    // have some results...
+    for (pNode = pRoot->children; pNode; pNode = pNode->next)
+    {
+        if (pNode && pNode->type == XML_ELEMENT_NODE)
+        {
+            if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("place")))
+            {
+                LocationInfo *pInfo = g_new0(LocationInfo, 1);
+
+                /* preset units by locale */
+                pInfo->cUnits_ = cUnits;
+                /* validate and process all fields */
+                if (processOSMPlace(pInfo, pNode))
+                    list = g_list_prepend(list, pInfo);
+            }
+        }// end if element
+    }// end for pRoot children
+
+    xmlFreeDoc(pDoc);
+
+    return g_list_reverse(list);
+}
+
+/**
+ * Retrieves the details for the specified location from OpenStreetMap server
+ *
+ * @param pczLocation The string containing the name/code of the location
+ *
+ * @return A pointer to a list of LocationInfo entries, possibly empty,
+ *         if no details were found. Caller is responsible for freeing the list.
+ */
+GList *
+getOSMLocationInfo(ProviderInfo * instance, const gchar * pczLocation)
+{
+    GList * pList = NULL;
+    gchar * pcEscapedLocation = convertToASCII(pczLocation);
+    gchar * cQuery = g_strdup_printf("https://nominatim.openstreetmap.org/search?"
+                                     "q=%s&format=xml", pcEscapedLocation);
+    const gchar * locale;
+    struct utsname uts;
+    gchar * pResponse = NULL;
+    CURLcode iRetCode;
+    gint iDataSize = 0;
+    char userAgentHeader[128];
+    char languageHeader[32];
+    const char *headers[] = { userAgentHeader, languageHeader, NULL };
+
+    /* parse and search */
+    g_free(pcEscapedLocation);
+    locale = setlocale(LC_MESSAGES, NULL);
+    if (!locale)
+        locale = "en";
+    uname(&uts);
+
+    snprintf(languageHeader, sizeof(languageHeader), "Accept-Language: %.2s,en",
+             locale);
+    snprintf(userAgentHeader, sizeof(userAgentHeader), "User-Agent: " PACKAGE "/" VERSION "(%s %s)",
+             uts.sysname, uts.machine);
+
+    //g_debug("cQuery %s",cQuery);
+    //g_debug("userAgentHeader %s",userAgentHeader);
+
+    LXW_LOG(LXW_DEBUG, "openweathermap::getLocationInfo(%s): query[%d]: %s",
+            pczLocation, iRet, cQuery);
+
+    iRetCode = getURL(cQuery, &pResponse, &iDataSize, headers);
+
+    //g_debug("pResponse %s",pResponse);
+    g_free(cQuery);
+
+    if (!pResponse || iRetCode != CURLE_OK)
+    {
+        LXW_LOG(LXW_ERROR, "openweathermap::getLocationInfo(%s): Failed with error code %d",
+                pczLocation, iRetCode);
+    }
+    else
+    {
+        LXW_LOG(LXW_DEBUG, "openweathermap::getLocationInfo(%s): Response code: %d, size: %d",
+                pczLocation, iRetCode, iDataSize);
+
+        LXW_LOG(LXW_VERBOSE, "openweathermap::getLocation(%s): Contents: %s",
+                pczLocation, (const char *)pResponse);
+
+        pList = parseOSMResponse(pResponse, locale);
+    }
+
+    g_free(pResponse);
+
+    return pList;
+}
+
+/**
+ * Retrieves the forecast for the specified location WOEID
+ *
+ * @param pczWOEID The string containing the WOEID of the location
+ * @param czUnits The character containing the units for the forecast (c|f)
+ * @param pForecast The pointer to the forecast to be filled. If set to NULL,
+ *                  a new one will be allocated.
+ *
+ */
+static ForecastInfo *getForecastInfo(ProviderInfo *instance,
+                                     LocationInfo *location,
+                                     ForecastInfo *lastForecast)
+{
+  CURLcode iRetCode = 0;
+  gint iDataSize = 0;
+  gint iRet;
+  gchar * cQueryBuffer = getForecastQuery(location->dLatitude_,
+                                          location->dLongitude_,
+                                          location->cUnits_, instance->wLang);
+  ForecastInfo *pForecast = lastForecast;
+  char * pResponse = NULL;
+
+  LXW_LOG(LXW_DEBUG, "openweathermap::getForecastInfo(%s): query[%d]: %s",
+          pczWOEID, iRet, cQueryBuffer);
+//g_debug("query: %s",cQueryBuffer);
+
+  iRetCode = getURL(cQueryBuffer, &pResponse, &iDataSize, NULL);
+//g_debug("response: %s",pResponse);
+
+  if (!pResponse || iRetCode != CURLE_OK)
+    {
+      LXW_LOG(LXW_ERROR, "openweathermap::getForecastInfo(%s): Failed with error code %d",
+              pczWOEID, iRetCode);
+    }
+  else
+    {
+      LXW_LOG(LXW_DEBUG, "openweathermap::getForecastInfo(%s): Response code: %d, size: %d",
+              pczWOEID, iRetCode, iDataSize);
+
+      LXW_LOG(LXW_VERBOSE, "openweathermap::getForecastInfo(%s): Contents: %s",
+              pczWOEID, (const char *)pResponse);
+
+      iRet = parseResponse(pResponse, NULL, &pForecast, location->cUnits_);
+
+      LXW_LOG(LXW_DEBUG, "openweathermap::getForecastInfo(%s): Response parsing returned %d",
+              pczWOEID, iRet);
+
+      if (iRet)
+        {
+          freeForecast(pForecast);
+          pForecast = NULL;
+        }
+      else
+        pForecast->iWindChill_ = -1000; /* set it to invalid value */
+    }
+
+  g_free(cQueryBuffer);
+  g_free(pResponse);
+
+  return pForecast;
+}
+
+provider_callback_info OpenWeatherMapCallbacks = {
+  .name = "openweathermap",
+  .description = N_("OpenWeatherMap"),
+  .initProvider = initOWM,
+  .freeProvider = freeOWM,
+  .getLocationInfo = getOSMLocationInfo,
+  .getForecastInfo = getForecastInfo,
+  .supports_woeid = FALSE
+};
diff --git a/plugins/weather/openweathermap.h b/plugins/weather/openweathermap.h
new file mode 100644 (file)
index 0000000..2b50c1a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012-2014 Piotr Sipika.
+ * Copyright (C) 2019 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * See the COPYRIGHT file for more information.
+ */
+
+#ifndef _OPENWEATHERMAP_H_
+#define _OPENWEATHERMAP_H_ 1
+
+#include "providers.h"
+
+/* retrieved from openweathermap.org */
+#define WEATHER_APPID "42005bf4482b716fb9646286ca99a2a7"
+
+extern provider_callback_info OpenWeatherMapCallbacks;
+
+GList *getOSMLocationInfo(ProviderInfo * instance, const gchar * pczLocation);
+
+#endif /* _OPENWEATHERMAP_H_ */
diff --git a/plugins/weather/providers.h b/plugins/weather/providers.h
new file mode 100644 (file)
index 0000000..a53f1b9
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 Andriy Grutsenko <andrej@rep.kiev.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * See the COPYRIGHT file for more information.
+ */
+
+#ifndef _PROVIDERS_H_
+#define _PROVIDERS_H_ 1
+
+#include "forecast.h"
+#include "location.h"
+
+#include <glib.h>
+
+typedef struct ProviderInfo ProviderInfo;
+
+typedef struct {
+    const char *name;
+    const char *description;
+    ProviderInfo * (*initProvider)(void);
+    void (*freeProvider)(ProviderInfo *instance);
+    GList * (*getLocationInfo)(ProviderInfo *instance, const gchar *pattern);
+    ForecastInfo * (*getForecastInfo)(ProviderInfo *instance,
+                                      LocationInfo *location,
+                                      ForecastInfo *last);
+    gboolean supports_woeid;
+} provider_callback_info;
+
+#endif /* _PROVIDERS_H_ */
index 7c9af86..522938a 100644 (file)
@@ -24,6 +24,8 @@
 #include "weatherwidget.h"
 #include "yahooutil.h"
 #include "logutil.h"
+#include "providers.h"
+#include "openweathermap.h"
 
 #include "plugin.h"
 
 #include <gtk/gtk.h>
 #include <stdlib.h>
 
+static provider_callback_info *providersList[] = {
+/*  &YahooCallbacks, -- does not work anymore */
+  &OpenWeatherMapCallbacks,
+  NULL
+};
+
 /* Need to maintain count for bookkeeping */
 static gint g_iCount = 0;
 
 typedef struct
 {
   gint iMyId_;
-  GtkWidget *pWeather_;
+  GtkWeather *pWeather_;
   config_setting_t *pConfig_;
   LXPanel *pPanel_;
 } WeatherPluginPrivate;
@@ -62,8 +70,6 @@ weather_destructor(gpointer pData)
 
   if (g_iCount == 0)
     {
-      cleanupYahooUtil();
-
       cleanupLogUtil();
     }
 }
@@ -79,7 +85,13 @@ weather_destructor(gpointer pData)
 static GtkWidget *
 weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
 {
-  WeatherPluginPrivate * pPriv = g_new0(WeatherPluginPrivate, 1);
+  WeatherPluginPrivate *pPriv;
+  const char *pczDummy;
+  int iDummyVal;
+  int locSet = 0;
+  provider_callback_info **pProvider;
+
+  pPriv = g_new0(WeatherPluginPrivate, 1);
 
   pPriv->pConfig_ = pConfig;
   pPriv->pPanel_ = pPanel;
@@ -94,20 +106,53 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
       initializeLogUtil("syslog");
       
       setMaxLogLevel(LXW_ERROR);
-
-      initializeYahooUtil();
     }
 
   LXW_LOG(LXW_DEBUG, "weather_constructor()");
   
-  GtkWidget * pWidg = gtk_weather_new();
+  GtkWeather * pWidg = gtk_weather_new();
 
   pPriv->pWeather_ = pWidg;
 
+  /* Try to get a provider */
+  if (config_setting_lookup_string(pConfig, "provider", &pczDummy))
+    {
+      for (pProvider = providersList; *pProvider; pProvider++)
+        {
+          if (strcmp((*pProvider)->name, pczDummy) == 0)
+            {
+              locSet = gtk_weather_set_provider(pWidg, *pProvider);
+              break;
+            }
+        }
+    }
+
+  /* No working provider selected, let try some */
+  if (!locSet)
+    {
+      for (pProvider = providersList; *pProvider; pProvider++)
+        {
+          locSet = gtk_weather_set_provider(pWidg, *pProvider);
+          if (locSet)
+            break;
+        }
+    }
+
+  /* No working provider found, retreat */
+  if (!locSet)
+  {
+    gtk_widget_destroy(GTK_WIDGET(pWidg));
+    g_free(pPriv);
+    --g_iCount;
+    if (g_iCount == 0)
+      cleanupLogUtil();
+    return NULL;
+  }
+
   GtkWidget * pEventBox = gtk_event_box_new();
 
   lxpanel_plugin_set_data(pEventBox, pPriv, weather_destructor);
-  gtk_container_add(GTK_CONTAINER(pEventBox), pWidg);
+  gtk_container_add(GTK_CONTAINER(pEventBox), GTK_WIDGET(pWidg));
 
   gtk_widget_set_has_window(pEventBox, FALSE);
 
@@ -115,8 +160,6 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
 
   /* use config settings */
   LocationInfo * pLocation = g_new0(LocationInfo, 1);
-  const char *pczDummy = NULL;
-  int iDummyVal = 0;
 
   if (config_setting_lookup_string(pConfig, "alias", &pczDummy))
     {
@@ -158,13 +201,32 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
       LXW_LOG(LXW_ERROR, "Weather: could not lookup country in config.");
     }
 
-  if (config_setting_lookup_string(pConfig, "woeid", &pczDummy))
+  iDummyVal = 0;
+  locSet = 0;
+  pLocation->dLongitude_ = 360.0; /* invalid value */
+  pLocation->dLatitude_ = 360.0;
+  if (config_setting_lookup_string(pConfig, "longitude", &pczDummy))
+    {
+      pLocation->dLongitude_ = g_strtod(pczDummy, NULL);
+      iDummyVal++;
+    }
+  if (iDummyVal && config_setting_lookup_string(pConfig, "latitude", &pczDummy))
+    {
+      pLocation->dLatitude_ = g_strtod(pczDummy, NULL);
+      locSet = 1;
+    }
+  /* no coords found, let try woeid if provider works with it though */
+  else if ((*pProvider)->supports_woeid &&
+           config_setting_lookup_string(pConfig, "woeid", &pczDummy))
     {
       pLocation->pcWOEID_ = g_strndup(pczDummy, (pczDummy) ? strlen(pczDummy) : 0);
+      locSet = 1;
     }
-  else if (config_setting_lookup_int(pConfig, "woeid", &iDummyVal))
+  else if ((*pProvider)->supports_woeid &&
+           config_setting_lookup_int(pConfig, "woeid", &iDummyVal))
     {
       pLocation->pcWOEID_ = g_strdup_printf("%d", iDummyVal);
+      locSet = 1;
     }
   else
     {
@@ -182,6 +244,8 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
 
   if (config_setting_lookup_int(pConfig, "interval", &iDummyVal))
     {
+      if (iDummyVal < 20) /* Minimum 20 minutes */
+        iDummyVal = 60; /* Set to default 60 minutes */
       pLocation->uiInterval_ = (guint)iDummyVal;
     }
   else
@@ -189,7 +253,6 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
       LXW_LOG(LXW_ERROR, "Weather: could not lookup interval in config.");
     }
 
-  iDummyVal = 0;
   if (config_setting_lookup_int(pConfig, "enabled", &iDummyVal))
     {
       pLocation->bEnabled_ = (gint)iDummyVal;
@@ -199,7 +262,7 @@ weather_constructor(LXPanel *pPanel, config_setting_t *pConfig)
       LXW_LOG(LXW_ERROR, "Weather: could not lookup enabled flag in config.");
     }
 
-  if (pLocation->pcAlias_ && pLocation->pcWOEID_)
+  if (pLocation->pcAlias_ && locSet)
     {
       GValue locationValue = G_VALUE_INIT;
 
@@ -227,6 +290,7 @@ void weather_save_configuration(GtkWidget * pWeather, LocationInfo * pLocation)
 {
   GtkWidget * pWidget = gtk_widget_get_parent(pWeather);
   WeatherPluginPrivate * pPriv = NULL;
+  provider_callback_info * pProvider;
 
   if (pWidget)
     {
@@ -249,16 +313,32 @@ void weather_save_configuration(GtkWidget * pWeather, LocationInfo * pLocation)
       config_group_set_string(pPriv->pConfig_, "country", pLocation->pcCountry_);
       config_group_set_string(pPriv->pConfig_, "woeid", pLocation->pcWOEID_);
 
-      char units[2] = {0};
-      if (snprintf(units, 2, "%c", pLocation->cUnits_) > 0)
+      char buff[16];
+      if (snprintf(buff, 2, "%c", pLocation->cUnits_) > 0)
         {
-          config_group_set_string(pPriv->pConfig_, "units", units);
+          config_group_set_string(pPriv->pConfig_, "units", buff);
+        }
+
+      if (pLocation->dLatitude_ < 360.0)
+        {
+          snprintf(buff, sizeof(buff), "%.6f", pLocation->dLatitude_);
+          config_group_set_string(pPriv->pConfig_, "latitude", buff);
+        }
+      if (pLocation->dLongitude_ < 360.0)
+        {
+          snprintf(buff, sizeof(buff), "%.6f", pLocation->dLongitude_);
+          config_group_set_string(pPriv->pConfig_, "longitude", buff);
         }
 
       config_group_set_int(pPriv->pConfig_, "interval", (int) pLocation->uiInterval_);
       config_group_set_int(pPriv->pConfig_, "enabled", (int) pLocation->bEnabled_);
     }
 
+  pProvider = gtk_weather_get_provider(GTK_WEATHER(pWeather));
+  if (pProvider)
+    {
+      config_group_set_string(pPriv->pConfig_, "provider", pProvider->name);
+    }
 }
 
 void weather_set_label_text(GtkWidget * pWeather, GtkWidget * label, const gchar * text)
@@ -300,9 +380,7 @@ weather_configuration_changed(LXPanel *pPanel, GtkWidget *pWidget)
               panel_get_icon_size(pPanel));
 
       WeatherPluginPrivate * pPriv = (WeatherPluginPrivate *) lxpanel_plugin_get_data(pWidget);
-      GtkWeather * weather = GTK_WEATHER(pPriv->pWeather_);
-      gtk_weather_render(weather);
-
+      gtk_weather_render(pPriv->pWeather_);
     }
 }
 
@@ -322,7 +400,7 @@ weather_configure(LXPanel *pPanel G_GNUC_UNUSED, GtkWidget *pWidget)
 
   WeatherPluginPrivate * pPriv = (WeatherPluginPrivate *) lxpanel_plugin_get_data(pWidget);
 
-  GtkWidget * pDialog = gtk_weather_create_preferences_dialog(GTK_WIDGET(pPriv->pWeather_));
+  GtkWidget * pDialog = gtk_weather_create_preferences_dialog(pPriv->pWeather_, providersList);
 
   return pDialog;
 }
index eea608d..318253a 100644 (file)
@@ -87,6 +87,7 @@ struct _PreferencesDialogData
   GtkWidget * manual_button;
   GtkWidget * auto_button;
   GtkWidget * auto_spin_button;
+  GtkWidget * provider_button;
 };
 
 struct _LocationThreadData
@@ -117,9 +118,11 @@ struct _GtkWeatherPrivate
   GtkWidget * conditions_dialog;
 
   /* Internal data */
-  gpointer    previous_location;
-  gpointer    location;
-  gpointer    forecast;
+  provider_callback_info * provider;
+  ProviderInfo * provider_instance;
+  LocationInfo * previous_location;
+  LocationInfo * location;
+  ForecastInfo * forecast;
 
   /* Data for location and forecast retrieval threads */
   LocationThreadData location_data;
@@ -155,7 +158,7 @@ static void gtk_weather_get_property (GObject * object, guint prop_id,
                                       GValue * value, GParamSpec * param_spec);
 
 static void gtk_weather_set_location (GtkWeather * weather, gpointer location);
-static void gtk_weather_set_forecast (GtkWeather * weather, gpointer forecast);
+static void gtk_weather_set_forecast (GtkWeather * weather, ForecastInfo * forecast);
 
 static gboolean gtk_weather_button_pressed  (GtkWidget * widget, GdkEventButton * event);
 static gboolean gtk_weather_key_pressed     (GtkWidget * widget, GdkEventKey * event, gpointer data);
@@ -171,7 +174,7 @@ static void gtk_weather_show_location_progress_bar   (GtkWeather * weather);
 static void gtk_weather_show_location_list           (GtkWeather * weather, GList * list);
 static void gtk_weather_update_preferences_dialog    (GtkWeather * weather);
 
-static void gtk_weather_get_forecast (GtkWidget * widget);
+static void gtk_weather_get_forecast (GtkWeather * weather);
 
 static void gtk_weather_run_error_dialog (GtkWindow * parent, gchar * error_msg);
 
@@ -238,12 +241,12 @@ gtk_weather_get_type(void)
  *
  * @return A new instance of this widget type.
  */
-GtkWidget *
+GtkWeather *
 gtk_weather_new(void)
 {
   GObject * object = g_object_new(gtk_weather_get_type(), NULL);
 
-  return GTK_WIDGET(object);
+  return GTK_WEATHER(object);
 }
 
 /**
@@ -372,7 +375,10 @@ gtk_weather_destroy(GObject * object)
       g_source_remove(priv->forecast_data.timerid);
       priv->forecast_data.timerid = 0;
     }
-  
+
+  if (priv->provider)
+    priv->provider->freeProvider(priv->provider_instance);
+
   /* Need to free location and forecast. */
   freeLocation(priv->previous_location);
   freeLocation(priv->location);
@@ -462,7 +468,7 @@ gtk_weather_render(GtkWeather * weather)
   if (priv->location && priv->forecast)
     {
       /*LocationInfo * location = (LocationInfo *)priv->location;*/
-      ForecastInfo * forecast = (ForecastInfo *)priv->forecast;
+      ForecastInfo * forecast = priv->forecast;
 
       GtkRequisition req;
 
@@ -520,7 +526,7 @@ gtk_weather_render(GtkWeather * weather)
     }
 
   /* update tooltip with proper data... */
-  gchar * tooltip_text = gtk_weather_get_tooltip_text(GTK_WIDGET(weather));
+  gchar * tooltip_text = gtk_weather_get_tooltip_text(weather);
 
   gtk_widget_set_tooltip_text(GTK_WIDGET(weather), tooltip_text);
 
@@ -559,7 +565,7 @@ gtk_weather_set_property(GObject * object,
       copyLocation(&priv->previous_location, priv->location);
 
       /* The function starts timer if enabled, otherwise runs a single call. */
-      gtk_weather_get_forecast(GTK_WIDGET(weather));
+      gtk_weather_get_forecast(weather);
 
       break;
 
@@ -658,7 +664,7 @@ gtk_weather_set_location(GtkWeather * weather, gpointer location)
  *
  */
 static void
-gtk_weather_set_forecast(GtkWeather * weather, gpointer forecast)
+gtk_weather_set_forecast(GtkWeather * weather, ForecastInfo * forecast)
 {
   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
@@ -683,6 +689,32 @@ gtk_weather_set_forecast(GtkWeather * weather, gpointer forecast)
   g_signal_emit_by_name(weather, "forecast-changed", forecast);
 }
 
+provider_callback_info * gtk_weather_get_provider(GtkWeather * weather)
+{
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
+
+  return priv->provider;
+}
+
+int gtk_weather_set_provider(GtkWeather * weather, provider_callback_info * provider)
+{
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
+  ProviderInfo * instance = NULL;
+
+  if (provider)
+    instance = provider->initProvider();
+
+  if (instance == NULL) /* failed to init */
+    return 0;
+
+  if (priv->provider)
+    priv->provider->freeProvider(priv->provider_instance);
+
+  priv->provider = provider;
+  priv->provider_instance = instance;
+  return 1;
+}
+
 
 /* Action callbacks (button/cursor/key) */
 /**
@@ -699,13 +731,14 @@ gtk_weather_button_pressed(GtkWidget * widget, GdkEventButton * event)
   LXW_LOG(LXW_DEBUG, "GtkWeather::button_pressed(): Button: %d, type: %d", 
           event->button, event->type);
 
-  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
+  GtkWeather * weather = GTK_WEATHER(widget);
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
 #ifdef USE_STANDALONE
   /* If right-clicked, show popup */
   if (event->button == 3 && (event->type == GDK_BUTTON_PRESS))
     {
-      gtk_weather_run_popup_menu(widget);
+      gtk_weather_run_popup_menu(weather);
 
       return TRUE;
     }
@@ -715,7 +748,7 @@ gtk_weather_button_pressed(GtkWidget * widget, GdkEventButton * event)
       if (priv->conditions_dialog)
         gtk_dialog_response(GTK_DIALOG(priv->conditions_dialog), GTK_RESPONSE_ACCEPT);
       else
-        gtk_weather_run_conditions_dialog(widget);
+        gtk_weather_run_conditions_dialog(weather);
 
       return TRUE;
     }
@@ -735,7 +768,7 @@ gtk_weather_auto_update_toggled(GtkWidget * widget)
 
   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
 
-  LocationInfo * location = (LocationInfo *)priv->location;
+  LocationInfo * location = priv->location;
 
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->preferences_data.auto_button)) &&
       priv->location)
@@ -850,7 +883,8 @@ gtk_weather_change_location(GtkWidget * widget, GdkEventButton * event)
               LOG_ERRNO(ret, "pthread_attr_init");
             }
 
-          ret = pthread_create(&tid, &tattr, &gtk_weather_get_location_threadfunc, new_location);
+          priv->location_data.location = new_location;
+          ret = pthread_create(&tid, &tattr, &gtk_weather_get_location_threadfunc, priv);
 
           if (ret != 0)
             {
@@ -865,7 +899,6 @@ gtk_weather_change_location(GtkWidget * widget, GdkEventButton * event)
             }
       
           priv->location_data.tid = &tid;
-          priv->location_data.location = new_location;
 
           /* show progress bar and lookup selected location */
           gtk_weather_show_location_progress_bar(GTK_WEATHER(widget));
@@ -899,7 +932,7 @@ gtk_weather_change_location(GtkWidget * widget, GdkEventButton * event)
                 }
           
               /* Free list */
-              g_list_free_full(list, freeLocation);
+              g_list_free_full(list, (GDestroyNotify)freeLocation);
           
               /* Repaint preferences dialog */
               gtk_weather_update_preferences_dialog(GTK_WEATHER(widget));
@@ -1070,12 +1103,12 @@ gtk_weather_create_popup_menu(GtkWeather * weather)
   g_signal_connect_swapped(G_OBJECT(priv->menu_data.preferences_item), 
                            "activate",
                            G_CALLBACK(gtk_weather_run_preferences_dialog),
-                           GTK_WIDGET(weather));
+                           weather);
 
   g_signal_connect_swapped(G_OBJECT(priv->menu_data.refresh_item),
                            "activate",
                            G_CALLBACK(gtk_weather_get_forecast),
-                           GTK_WIDGET(weather));
+                           weather);
 
   g_signal_connect_swapped(G_OBJECT(priv->menu_data.quit_item),
                            "activate",
@@ -1109,7 +1142,7 @@ gtk_weather_preferences_dialog_response(GtkDialog *dialog, gint response, gpoint
     case GTK_RESPONSE_ACCEPT:
       if (priv->location)
         {
-          LocationInfo * location = (LocationInfo *)priv->location;
+          LocationInfo * location = priv->location;
 
           setLocationAlias(priv->location, 
                            (gpointer)gtk_entry_get_text(GTK_ENTRY(priv->preferences_data.alias_entry)));
@@ -1130,8 +1163,20 @@ gtk_weather_preferences_dialog_response(GtkDialog *dialog, gint response, gpoint
           /* Set this location as the valid one */
           copyLocation(&priv->previous_location, priv->location);          
 
+          GtkTreeIter iter;
+          if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->preferences_data.provider_button),
+                                            &iter))
+            {
+              GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->preferences_data.provider_button));
+              provider_callback_info *provider;
+
+              gtk_tree_model_get(model, &iter, 1, (gpointer *)&provider, -1);
+              gtk_weather_set_provider(weather, provider);
+              // TODO: show error if failed
+            }
+
           /* get forecast */
-          gtk_weather_get_forecast(GTK_WIDGET(weather));
+          gtk_weather_get_forecast(weather);
 
           gtk_weather_render(weather);
 
@@ -1143,7 +1188,7 @@ gtk_weather_preferences_dialog_response(GtkDialog *dialog, gint response, gpoint
     case GTK_RESPONSE_REJECT:
       gtk_weather_set_location(weather, priv->previous_location);
       
-      gtk_weather_get_forecast(GTK_WIDGET(weather));
+      gtk_weather_get_forecast(weather);
 
       break;
     default:
@@ -1163,9 +1208,9 @@ gtk_weather_preferences_dialog_response(GtkDialog *dialog, gint response, gpoint
  * @param widget Pointer to the current instance of the weather widget.
  */
 void
-gtk_weather_run_popup_menu(GtkWidget * widget)
+gtk_weather_run_popup_menu(GtkWeather * weather)
 {
-  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
   LXW_LOG(LXW_DEBUG, "GtkWeather::popup_menu()");
 
@@ -1197,10 +1242,8 @@ gtk_weather_run_popup_menu(GtkWidget * widget)
  * @return pointer to the preferences dialog, or NULL on failure.
  */
 GtkWidget *
-gtk_weather_create_preferences_dialog(GtkWidget * widget)
+gtk_weather_create_preferences_dialog(GtkWeather * weather, provider_callback_info ** list)
 {
-  GtkWeather * weather = GTK_WEATHER(widget);
-
   /* @NOTE: watch for parent window when dealing with the plugin */
   /* @TODO: connect the response signal to the proper function */
   LXW_LOG(LXW_DEBUG, "GtkWeather::create_preferences_dialog()");
@@ -1232,7 +1275,7 @@ gtk_weather_create_preferences_dialog(GtkWidget * widget)
   g_signal_connect(G_OBJECT(priv->preferences_data.location_button),
                    "key-press-event",
                    G_CALLBACK(gtk_weather_key_pressed),
-                   (gpointer)widget);
+                   (gpointer)weather);
 
   g_signal_connect_swapped(G_OBJECT(priv->preferences_data.location_button),
                            "button-press-event", 
@@ -1314,21 +1357,21 @@ gtk_weather_create_preferences_dialog(GtkWidget * widget)
   g_signal_connect_swapped(G_OBJECT(priv->preferences_data.manual_button),
                            "toggled",
                            G_CALLBACK(gtk_weather_auto_update_toggled),
-                           widget);
+                           weather);
 
   g_signal_connect(G_OBJECT(priv->preferences_data.dialog),
                    "response",
                    G_CALLBACK(gtk_weather_preferences_dialog_response),
-                   widget);
+                   weather);
 
   /*  g_signal_connect_swapped(G_OBJECT(priv->preferences_data.auto_button),
                            "toggled",
                            G_CALLBACK(gtk_weather_auto_update_toggled),
-                           widget);*/
+                           weather);*/
 
   GtkWidget * auto_hbox = gtk_hbox_new(FALSE, 2);
 
-  priv->preferences_data.auto_spin_button = gtk_spin_button_new_with_range(1, 60, 1);
+  priv->preferences_data.auto_spin_button = gtk_spin_button_new_with_range(20, 120, 10);
   
   GtkWidget * auto_min_label = gtk_label_new(_("minutes"));
 
@@ -1341,9 +1384,31 @@ gtk_weather_create_preferences_dialog(GtkWidget * widget)
 
   GtkWidget * source_label = gtk_label_new(_("Source:"));
 
-  GtkWidget * yahoo_button = gtk_radio_button_new_with_mnemonic(NULL, "_Yahoo! Weather"); 
+  GtkListStore *provider_model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
+  gint provider_active = -1;
+  while (list && *list)
+    {
+      GtkTreeIter iter;
 
-  gtk_widget_set_sensitive(yahoo_button, FALSE);
+      gtk_list_store_append(provider_model, &iter);
+      gtk_list_store_set(provider_model, &iter, 0, _((*list)->description),
+                                                1, *list, -1);
+      if (*list == priv->provider)
+        {
+          GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(provider_model), &iter);
+          gint *indices = gtk_tree_path_get_indices(path);
+          provider_active = indices[0];
+          gtk_tree_path_free(path);
+        }
+      list++;
+    }
+  GtkWidget * provider_button = gtk_combo_box_new_with_model(GTK_TREE_MODEL(provider_model));
+  priv->preferences_data.provider_button = provider_button;
+  GtkCellRenderer * column = gtk_cell_renderer_text_new();
+  g_object_unref(provider_model);
+  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(provider_button), column, TRUE);
+  gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(provider_button), column, "text", 0, NULL);
+  gtk_combo_box_set_active(GTK_COMBO_BOX(provider_button), provider_active);
 
   gtk_table_attach(GTK_TABLE(forecast_table), 
                    update_label,
@@ -1367,7 +1432,7 @@ gtk_weather_create_preferences_dialog(GtkWidget * widget)
                    10,5);
 
   gtk_table_attach(GTK_TABLE(forecast_table), 
-                   yahoo_button,
+                   provider_button,
                    1,2,1,2,
                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
                    GTK_EXPAND | GTK_FILL | GTK_SHRINK,
@@ -1399,10 +1464,8 @@ gtk_weather_create_preferences_dialog(GtkWidget * widget)
  * @param widget Pointer to the current instance of the weather object.
  */
 void
-gtk_weather_run_preferences_dialog(GtkWidget * widget)
+gtk_weather_run_preferences_dialog(GtkWeather * weather)
 {
-  GtkWeather * weather = GTK_WEATHER(widget);
-
   /* @NOTE: watch for parent window when dealing with the plugin */
   LXW_LOG(LXW_DEBUG, "GtkWeather::run_preferences_dialog()");
 
@@ -1414,7 +1477,7 @@ gtk_weather_run_preferences_dialog(GtkWidget * widget)
     }
 
   /* this dialog is the same one as priv->preferences_data.dialog */
-  GtkWidget * dialog = gtk_weather_create_preferences_dialog(widget);
+  GtkWidget * dialog = gtk_weather_create_preferences_dialog(weather);
 
   g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(gtk_widget_destroy), NULL);
 
@@ -1443,13 +1506,13 @@ gtk_weather_update_preferences_dialog(GtkWeather * weather)
 
   if (priv->location)
     {
-      LocationInfo * location = (LocationInfo *)priv->location;
+      LocationInfo * location = priv->location;
 
       /* populate location_label */
       gchar * loc = g_strconcat((location->pcCity_)?location->pcCity_:"",
-                                (location->pcCity_)?", ":"",
-                                (location->pcState_)?location->pcState_:"",
                                 (location->pcState_)?", ":"",
+                                (location->pcState_)?location->pcState_:"",
+                                (location->pcCountry_)?", ":"",
                                 (location->pcCountry_)?location->pcCountry_:"",
                                 NULL);
 
@@ -1459,7 +1522,12 @@ gtk_weather_update_preferences_dialog(GtkWeather * weather)
 
       /* populate the alias entry with pcAlias_ */
       gtk_widget_set_sensitive(priv->preferences_data.alias_entry, TRUE);
-      gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), location->pcAlias_);
+      if (location->pcAlias_)
+        gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), location->pcAlias_);
+      else if (location->pcCity_)
+        gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), location->pcCity_);
+      else
+        gtk_entry_set_text(GTK_ENTRY(priv->preferences_data.alias_entry), location->pcState_);
 
       gtk_widget_set_sensitive(priv->preferences_data.c_button, TRUE);
       gtk_widget_set_sensitive(priv->preferences_data.f_button, TRUE);
@@ -1486,7 +1554,7 @@ gtk_weather_update_preferences_dialog(GtkWeather * weather)
           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->preferences_data.manual_button), FALSE);
           gtk_widget_set_sensitive(GTK_WIDGET(priv->preferences_data.auto_spin_button), TRUE);
           gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->preferences_data.auto_spin_button), 
-                                    (gdouble)location->uiInterval_);
+                                    (gdouble)((location->uiInterval_) ? location->uiInterval_ : 60));
         }
       else
         {
@@ -1524,16 +1592,14 @@ gtk_weather_update_preferences_dialog(GtkWeather * weather)
  * @param widget Pointer to the current instance of the weather object.
  */
 void
-gtk_weather_run_conditions_dialog(GtkWidget * widget)
+gtk_weather_run_conditions_dialog(GtkWeather * weather)
 {
-  GtkWeather * weather = GTK_WEATHER(widget);
-
   LXW_LOG(LXW_DEBUG, "GtkWeather::run_conditions_dialog()");
 
   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
-  LocationInfo * location = (LocationInfo *)priv->location;
-  ForecastInfo * forecast = (ForecastInfo *)priv->forecast;
+  LocationInfo * location = priv->location;
+  ForecastInfo * forecast = priv->forecast;
 
   if (location && forecast)
     {
@@ -1561,9 +1627,9 @@ gtk_weather_run_conditions_dialog(GtkWidget * widget)
       GtkWidget * forecast_table = gtk_table_new(9, 2, FALSE);
 
       gchar * location_label_text = g_strconcat((location->pcCity_)?location->pcCity_:"",
-                                                (location->pcCity_)?", ":"",
-                                                (location->pcState_)?location->pcState_:"",
                                                 (location->pcState_)?", ":"",
+                                                (location->pcState_)?location->pcState_:"",
+                                                (location->pcCountry_)?", ":"",
                                                 (location->pcCountry_)?location->pcCountry_:"",
                                                 NULL);
 
@@ -1613,35 +1679,40 @@ gtk_weather_run_conditions_dialog(GtkWidget * widget)
                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
                        2,2);
 
-      gchar * feels = g_strdup_printf("%d \302\260%s", 
-                          /* Yahoo reports chill always in Fahreheit degrees */
-                                      (location->cUnits_ == 'c') ?
-                                      (forecast->iWindChill_ - 32) * 5 / 9 :
-                                      forecast->iWindChill_,
-                                      forecast->units_.pcTemperature_);
-
-      GtkWidget * feels_label = gtk_label_new(_("Feels like:"));
-      GtkWidget * feels_text = gtk_label_new(feels);
-
-      GtkWidget * feels_alignment = gtk_alignment_new(0, 0.5, 0, 0);
-      gtk_container_add(GTK_CONTAINER(feels_alignment), feels_label);
-
-      GtkWidget * feels_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
-      gtk_container_add(GTK_CONTAINER(feels_text_alignment), feels_text);
-
-      gtk_table_attach(GTK_TABLE(forecast_table), 
-                       feels_alignment,
-                       0,1,2,3,
-                       GTK_EXPAND | GTK_FILL | GTK_SHRINK,
-                       GTK_EXPAND | GTK_FILL | GTK_SHRINK,
-                       2,2);
+      gchar * feels = NULL;
 
-      gtk_table_attach(GTK_TABLE(forecast_table), 
-                       feels_text_alignment,
-                       1,2,2,3,
-                       GTK_EXPAND | GTK_FILL | GTK_SHRINK,
-                       GTK_EXPAND | GTK_FILL | GTK_SHRINK,
-                       2,2);
+      if (forecast->iWindChill_ > -1000) /* has a valid value */
+        {
+          feels = g_strdup_printf("%d \302\260%s",
+                          /* Yahoo reports chill always in Fahreheit degrees */
+                                  (location->cUnits_ == 'c') ?
+                                  (forecast->iWindChill_ - 32) * 5 / 9 :
+                                  forecast->iWindChill_,
+                                  forecast->units_.pcTemperature_);
+
+          GtkWidget * feels_label = gtk_label_new(_("Feels like:"));
+          GtkWidget * feels_text = gtk_label_new(feels);
+
+          GtkWidget * feels_alignment = gtk_alignment_new(0, 0.5, 0, 0);
+          gtk_container_add(GTK_CONTAINER(feels_alignment), feels_label);
+
+          GtkWidget * feels_text_alignment = gtk_alignment_new(0, 0.5, 0, 0);
+          gtk_container_add(GTK_CONTAINER(feels_text_alignment), feels_text);
+
+          gtk_table_attach(GTK_TABLE(forecast_table),
+                           feels_alignment,
+                           0,1,2,3,
+                           GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+                           GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+                           2,2);
+
+          gtk_table_attach(GTK_TABLE(forecast_table),
+                           feels_text_alignment,
+                           1,2,2,3,
+                           GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+                           GTK_EXPAND | GTK_FILL | GTK_SHRINK,
+                           2,2);
+        }
 
       gchar * humidity = g_strdup_printf("%d%%", forecast->iHumidity_);
 
@@ -1722,7 +1793,7 @@ gtk_weather_run_conditions_dialog(GtkWidget * widget)
                        GTK_EXPAND | GTK_FILL | GTK_SHRINK,
                        2,2);
 
-      gchar * wind = g_strdup_printf("%s %d %s", 
+      gchar * wind = g_strdup_printf("%s, %d %s",
                                      forecast->pcWindDirection_,
                                      forecast->iWindSpeed_,
                                      forecast->units_.pcSpeed_);
@@ -1865,7 +1936,7 @@ gtk_weather_run_conditions_dialog(GtkWidget * widget)
 
           if (response == GTK_RESPONSE_APPLY)
             {
-              gtk_weather_get_forecast(widget);
+              gtk_weather_get_forecast(weather);
             }
 
         }  while (response != GTK_RESPONSE_ACCEPT);
@@ -2174,9 +2245,9 @@ gtk_weather_show_location_list(GtkWeather * weather, GList * list)
  *         the memory using g_free.
  */
 gchar *
-gtk_weather_get_tooltip_text(GtkWidget * widget)
+gtk_weather_get_tooltip_text(GtkWeather * weather)
 {
-  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
   LXW_LOG(LXW_DEBUG, "GtkWeather::get_tooltip_text()");
 
@@ -2191,6 +2262,7 @@ gtk_weather_get_tooltip_text(GtkWidget * widget)
                                             forecast->iTemperature_,
                                             forecast->units_.pcTemperature_);
 
+#if 0 // TODO!
       gchar * today = g_strdup_printf("%s %d\302\260 / %d\302\260",
                                       _(forecast->today_.pcConditions_),
                                       forecast->today_.iLow_,
@@ -2200,23 +2272,27 @@ gtk_weather_get_tooltip_text(GtkWidget * widget)
                                          _(forecast->tomorrow_.pcConditions_),
                                          forecast->tomorrow_.iLow_,
                                          forecast->tomorrow_.iHigh_);
-
+#endif
       /* make it nice and pretty */
       tooltip_text = g_strconcat(_("Currently in "),location->pcAlias_, ": ",
                                  _(forecast->pcConditions_), " ", temperature, "",
+#if 0 // TODO!
                                  _("Today: "), today, "\n",
                                  _("Tomorrow: "), tomorrow,
+#endif
                                  NULL);
                                  
       g_free(temperature);
+#if 0 // TODO!
       g_free(today);
       g_free(tomorrow);
+#endif
 
     }
   else if (priv->location)
     {
       tooltip_text = g_strdup_printf(_("Forecast for %s unavailable."),
-                                     ((LocationInfo *)priv->location)->pcAlias_);
+                                     priv->location->pcAlias_);
     }
   else
     {
@@ -2259,18 +2335,18 @@ gtk_weather_set_window_icon(GtkWindow * window, gchar * icon_id)
  * @param widget Pointer to the current instance of the weather widget
  */
 static void
-gtk_weather_get_forecast(GtkWidget * widget)
+gtk_weather_get_forecast(GtkWeather * weather)
 {
   LXW_LOG(LXW_DEBUG, "GtkWeather::get_forecast()");
 
-  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(widget));
+  GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(weather);
 
-  LocationInfo * location = (LocationInfo *)priv->location;
+  LocationInfo * location = priv->location;
 
   if (location && location->bEnabled_)
     {      
       /* just to be sure... */
-      guint interval_in_seconds = 60 * ((location->uiInterval_) ? location->uiInterval_ : 1);
+      guint interval_in_seconds = 60 * ((location->uiInterval_) ? location->uiInterval_ : 60);
 
       if (priv->forecast_data.timerid > 0)
         {
@@ -2280,7 +2356,7 @@ gtk_weather_get_forecast(GtkWidget * widget)
       /* start forecast thread here */
       priv->forecast_data.timerid = g_timeout_add_seconds(interval_in_seconds,
                                                           gtk_weather_get_forecast_timerfunc,
-                                                          (gpointer)widget);
+                                                          (gpointer)weather);
       
     }
   else
@@ -2296,7 +2372,7 @@ gtk_weather_get_forecast(GtkWidget * widget)
   /* One, single call just to get the latest forecast */
   if (location)
     {
-      gtk_weather_get_forecast_timerfunc((gpointer)widget);
+      gtk_weather_get_forecast_timerfunc((gpointer)weather);
     }
 }
 
@@ -2310,11 +2386,12 @@ gtk_weather_get_forecast(GtkWidget * widget)
 static void *
 gtk_weather_get_location_threadfunc(void * arg)
 {
-  gchar * location = (gchar *)arg;
-  
-  GList * list = getLocationInfo(location);
+  GtkWeatherPrivate * priv = (GtkWeatherPrivate *)arg;
 
-  g_list_foreach(list, setLocationAlias, (gpointer)location);
+  GList * list = priv->provider->getLocationInfo(priv->provider_instance,
+                                                 priv->location_data.location);
+
+  g_list_foreach(list, (GFunc)setLocationAlias, (gpointer)priv->location_data.location);
 
   return list;  
 }
@@ -2332,19 +2409,18 @@ gtk_weather_get_forecast_timerfunc(gpointer data)
   GtkWeatherPrivate * priv = GTK_WEATHER_GET_PRIVATE(GTK_WEATHER(data));
 
   LXW_LOG(LXW_DEBUG, "GtkWeather::get_forecast_timerfunc(%d %d)", 
-          (priv->location)?((LocationInfo*)priv->location)->bEnabled_:0,
-          (priv->location)?((LocationInfo*)priv->location)->uiInterval_ * 60:0);
+          (priv->location)?priv->location->bEnabled_:0,
+          (priv->location)?priv->location->uiInterval_ * 60:0);
 
   if (!priv->location)
     {
       return FALSE;
     }
 
-  LocationInfo * location = (LocationInfo *)priv->location;
-
-  getForecastInfo(location->pcWOEID_, location->cUnits_, &priv->forecast);
+  priv->forecast = priv->provider->getForecastInfo(priv->provider_instance,
+                                                   priv->location, priv->forecast);
 
   gtk_weather_set_forecast(GTK_WEATHER(data), priv->forecast);
 
-  return location->bEnabled_;
+  return priv->location->bEnabled_;
 }
index 61058f2..c12913b 100644 (file)
@@ -23,6 +23,8 @@
 #ifndef __WEATHERWIDGET_H__
 #define __WEATHERWIDGET_H__
 
+#include "providers.h"
+
 #include <gtk/gtk.h>
 #include <glib.h>
 
@@ -55,14 +57,16 @@ struct _GtkWeatherClass
 };
 
 GType       gtk_weather_get_type(void) G_GNUC_CONST;
-GtkWidget * gtk_weather_new(void);
+GtkWeather * gtk_weather_new(void);
 #ifdef USE_STANDALONE
-void        gtk_weather_run_preferences_dialog(GtkWidget * widget);
-void        gtk_weather_run_popup_menu(GtkWidget * widget);
+void        gtk_weather_run_preferences_dialog(GtkWeather * weather);
+void        gtk_weather_run_popup_menu(GtkWeather * weather);
 #endif
-void        gtk_weather_run_conditions_dialog(GtkWidget * widget);
-gchar *     gtk_weather_get_tooltip_text(GtkWidget * widget);
-GtkWidget * gtk_weather_create_preferences_dialog(GtkWidget * widget);
+void        gtk_weather_run_conditions_dialog(GtkWeather * weather);
+gchar *     gtk_weather_get_tooltip_text(GtkWeather * weather);
+GtkWidget * gtk_weather_create_preferences_dialog(GtkWeather * weather, provider_callback_info ** list);
+int         gtk_weather_set_provider(GtkWeather * weather, provider_callback_info * provider);
+provider_callback_info * gtk_weather_get_provider(GtkWeather * weather);
 
 /* if USE_STANDALONE is used then application should provide these functions */
 void weather_save_configuration(GtkWidget * pWeather, LocationInfo * pLocation);
index b7c31b5..f39dd24 100644 (file)
@@ -24,6 +24,7 @@
 #include "location.h"
 #include "forecast.h"
 #include "logutil.h"
+#include "yahooutil.h"
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
@@ -34,6 +35,7 @@
 
 #include <gtk/gtk.h>
 #include <gio/gio.h>
+#include <glib/gi18n.h>
 
 #include <string.h>
 #include <stdio.h>
 #define CONSTCHAR_P(x) (const char *)(x)
 #define CHAR_P(x) (char *)(x)
 
-static gint g_iInitialized = 0;
+#define WIND_DIRECTION(x) ( \
+  ((x>=350 && x<=360) || (x>=0 && x<=11 ))?_("N"): \
+  (x>11   && x<=33 )?_("NNE"): \
+  (x>33   && x<=57 )?_("NE"):  \
+  (x>57   && x<=79 )?_("ENE"): \
+  (x>79   && x<=101)?_("E"):   \
+  (x>101  && x<=123)?_("ESE"): \
+  (x>123  && x<=147)?_("SE"):  \
+  (x>147  && x<=169)?_("SSE"): \
+  (x>169  && x<=192)?_("S"):   \
+  (x>192  && x<=214)?_("SSW"): \
+  (x>214  && x<=236)?_("SW"):  \
+  (x>236  && x<=258)?_("WSW"): \
+  (x>258  && x<=282)?_("W"):   \
+  (x>282  && x<=304)?_("WNW"): \
+  (x>304  && x<=326)?_("NW"):  \
+  (x>326  && x<=349)?_("NNW"):"")
+
+static gint g_iInitialized;
 
 static const gchar * WOEID_QUERY = "SELECT%20*%20FROM%20geo.places%20WHERE%20text=";
 static const gchar * FORECAST_QUERY_P1 = "SELECT%20*%20FROM%20weather.forecast%20WHERE%20woeid=";
 static const gchar * FORECAST_QUERY_P2 = "%20and%20u=";
 static const gchar * FORECAST_URL = "http://query.yahooapis.com/v1/public/yql?format=xml&q=";
 
+struct ProviderInfo {
+};
+
 /**
  * Returns the length for the appropriate WOEID query
  *
@@ -254,15 +277,16 @@ setImageIfDifferent(gchar ** pcStorage,
         }
       
       // retrieve the URL and create the new image
-      gint iRetCode = 0;
+      CURLcode iRetCode;
       gint iDataSize = 0;
+      gpointer pResponse = NULL;
+      iRetCode = getURL(pczNewURL, &pResponse, &iDataSize, NULL);
 
-      gpointer pResponse = getURL(pczNewURL, &iRetCode, &iDataSize);
-
-      if (!pResponse || iRetCode != HTTP_STATUS_OK)
+      if (!pResponse || iRetCode != CURLE_OK)
         {
           LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): Failed to get URL (%d, %d)", 
                   iRetCode, iDataSize);
+          g_free(pResponse);
 
           return -1;
         }
@@ -748,7 +772,7 @@ evaluateXPathExpression(xmlXPathContextPtr pContext, const char * pczExpression)
  *       'channel' for Forecast (pForecast)
  */
 static gint
-parseResponse(gpointer pResponse, GList ** pList, gpointer * pForecast)
+parseResponse(gpointer pResponse, GList ** pList, ForecastInfo ** pForecast)
 {
   int iLocation = (pList)?1:0;
 
@@ -898,27 +922,32 @@ parseResponse(gpointer pResponse, GList ** pList, gpointer * pForecast)
  * Initializes the internals: XML 
  *
  */
-void
+static ProviderInfo *
 initializeYahooUtil(void)
 {
+#if 0 /* does not work anymore */
   if (!g_iInitialized)
     {
       xmlInitParser();
 
       g_iInitialized = 1;
     }
+  return (ProviderInfo *)1;
+#else
+  return NULL;
+#endif
 }
 
 /**
  * Cleans up the internals: XML 
  *
  */
-void
-cleanupYahooUtil(void)
+static void
+cleanupYahooUtil(ProviderInfo *instance G_GNUC_UNUSED)
 {
   if (g_iInitialized)
     {
-      xmlCleanupParser();
+      // xmlCleanupParser(); // will crash if there is more than one libxml user
 
       g_iInitialized = 0;
     }
@@ -932,10 +961,10 @@ cleanupYahooUtil(void)
  * @return A pointer to a list of LocationInfo entries, possibly empty, 
  *         if no details were found. Caller is responsible for freeing the list.
  */
-GList *
-getLocationInfo(const gchar * pczLocation)
+static GList *
+getLocationInfo(ProviderInfo * instance G_GNUC_UNUSED, const gchar * pczLocation)
 {
-  gint iRetCode = 0;
+  CURLcode iRetCode;
   gint iDataSize = 0;
 
   GList * pList = NULL;
@@ -953,9 +982,11 @@ getLocationInfo(const gchar * pczLocation)
   LXW_LOG(LXW_DEBUG, "yahooutil::getLocationInfo(%s): query[%d]: %s",
           pczLocation, iRet, cQueryBuffer);
 
-  gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize);
+  gpointer pResponse = NULL;
+
+  iRetCode = getURL(cQueryBuffer, &pResponse, &iDataSize, NULL);
 
-  if (!pResponse || iRetCode != HTTP_STATUS_OK)
+  if (!pResponse || iRetCode != CURLE_OK)
     {
       LXW_LOG(LXW_ERROR, "yahooutil::getLocationInfo(%s): Failed with error code %d",
               pczLocation, iRetCode);
@@ -976,7 +1007,7 @@ getLocationInfo(const gchar * pczLocation)
       if (iRet)
         {
           // failure
-          g_list_free_full(pList, freeLocation);
+          g_list_free_full(pList, (GDestroyNotify)freeLocation);
         }
 
     }
@@ -996,50 +1027,66 @@ getLocationInfo(const gchar * pczLocation)
  *                  a new one will be allocated.
  *
  */
-void
-getForecastInfo(const gchar * pczWOEID, const gchar czUnits, gpointer * pForecast)
+static ForecastInfo *getForecastInfo(ProviderInfo *instance G_GNUC_UNUSED,
+                                     LocationInfo *location,
+                                     ForecastInfo *lastForecast)
 {
-  gint iRetCode = 0;
+  CURLcode iRetCode;
   gint iDataSize = 0;
+  ForecastInfo * pForecast = lastForecast;
 
-  gsize len = getForecastQueryLength(pczWOEID);
+  gsize len = getForecastQueryLength(location->pcWOEID_);
 
   gchar * cQueryBuffer = g_malloc(len + 1);
 
-  gint iRet = getForecastQuery(cQueryBuffer, pczWOEID, czUnits);
+  gint iRet = getForecastQuery(cQueryBuffer, location->pcWOEID_, location->cUnits_);
 
   LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): query[%d]: %s",
-          pczWOEID, iRet, cQueryBuffer);
+          location->pcWOEID_, iRet, cQueryBuffer);
 
-  gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize);
+  gpointer pResponse = NULL;
 
-  if (!pResponse || iRetCode != HTTP_STATUS_OK)
+  iRetCode = getURL(cQueryBuffer, &pResponse, &iDataSize, NULL);
+
+  if (!pResponse || iRetCode != CURLE_OK)
     {
       LXW_LOG(LXW_ERROR, "yahooutil::getForecastInfo(%s): Failed with error code %d",
-              pczWOEID, iRetCode);
+              location->pcWOEID_, iRetCode);
     }
   else
     {
       LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response code: %d, size: %d",
-              pczWOEID, iRetCode, iDataSize);
+              location->pcWOEID_, iRetCode, iDataSize);
 
       LXW_LOG(LXW_VERBOSE, "yahooutil::getForecastInfo(%s): Contents: %s",
-              pczWOEID, (const char *)pResponse);
+              location->pcWOEID_, (const char *)pResponse);
 
-      iRet = parseResponse(pResponse, NULL, pForecast);
+      iRet = parseResponse(pResponse, NULL, &pForecast);
     
       LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response parsing returned %d",
-              pczWOEID, iRet);
+              location->pcWOEID_, iRet);
 
       if (iRet)
         {
-          freeForecast(*pForecast);
+          freeForecast(pForecast);
 
-          *pForecast = NULL;
+          pForecast = NULL;
         }
 
     }
 
   g_free(cQueryBuffer);
   g_free(pResponse);
+
+  return pForecast;
 }
+
+provider_callback_info YahooCallbacks = {
+  .name = "yahoo",
+  .description = N_("Yahoo! Weather"),
+  .initProvider = initializeYahooUtil,
+  .freeProvider = cleanupYahooUtil,
+  .getLocationInfo = getLocationInfo,
+  .getForecastInfo = getForecastInfo,
+  .supports_woeid = TRUE
+};
index 8216a5d..b4784e7 100644 (file)
 #ifndef LXWEATHER_YAHOOUTIL_HEADER
 #define LXWEATHER_YAHOOUTIL_HEADER
 
-#include <glib.h>
+#include "providers.h"
 
-/**
- * Retrieves the details for the specified location
- *
- * @param pczLocation The string containing the name/code of the location
- *
- * @return A pointer to a list of LocationInfo entries, possibly empty, 
- *         if no details were found. Caller is responsible for freeing the list.
- */
-GList *
-getLocationInfo(const gchar * pczLocation);
-
-/**
- * Retrieves the forecast for the specified location WOEID
- *
- * @param pczWOEID The string containing the WOEID of the location
- * @param czUnits The character containing the units for the forecast (c|f)
- * @param pForecast The pointer to the forecast to be filled. If set to NULL,
- *                  a new one will be allocated.
- *
- */
-void
-getForecastInfo(const gchar * pczWOEID, const gchar czUnits, gpointer pForecast);
-
-/**
- * Initializes the internals: XML and HTTP
- *
- */
-void
-initializeYahooUtil(void);
-
-/**
- * Cleans up the internals: XML and HTTP
- *
- */
-void
-cleanupYahooUtil(void);
+extern provider_callback_info YahooCallbacks;
 
 #endif
index 40fb084..ad9364a 100644 (file)
@@ -50,3 +50,5 @@ plugins/monitors/monitors.c
 
 plugins/weather/weatherwidget.c
 plugins/weather/weather.c
+plugins/weather/yahooutil.c
+plugins/weather/openweathermap.c
index 9fce2c6..8d879b3 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-11-01 00:50+0200\n"
+"POT-Creation-Date: 2019-02-22 00:29+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -40,108 +40,108 @@ msgstr ""
 msgid "Available plugins"
 msgstr ""
 
-#: ../src/configurator.c:1428
+#: ../src/configurator.c:1427
 msgid "Logout command is not set"
 msgstr ""
 
-#: ../src/configurator.c:1496
+#: ../src/configurator.c:1495
 msgid "Select a directory"
 msgstr ""
 
-#: ../src/configurator.c:1496 ../src/configurator.c:1608
+#: ../src/configurator.c:1495 ../src/configurator.c:1607
 msgid "Select a file"
 msgstr ""
 
-#: ../src/configurator.c:1641
+#: ../src/configurator.c:1640
 msgid "_Browse"
 msgstr ""
 
-#: ../src/panel.c:1283
+#: ../src/panel.c:1292
 msgid "There is no room for another panel. All the edges are taken."
 msgstr ""
 
-#: ../src/panel.c:1309
+#: ../src/panel.c:1318
 msgid ""
 "Really delete this panel?\n"
 "<b>Warning: This can not be recovered.</b>"
 msgstr ""
 
-#: ../src/panel.c:1311
+#: ../src/panel.c:1320
 msgid "Confirm"
 msgstr ""
 
 #. TRANSLATORS: Replace this string with your names, one name per line.
-#: ../src/panel.c:1346
+#: ../src/panel.c:1355
 msgid "translator-credits"
 msgstr ""
 
-#: ../src/panel.c:1351
+#: ../src/panel.c:1360
 msgid "LXPanel"
 msgstr ""
 
-#: ../src/panel.c:1369
+#: ../src/panel.c:1378
 msgid "Copyright (C) 2008-2016"
 msgstr ""
 
-#: ../src/panel.c:1370
+#: ../src/panel.c:1379
 msgid "Desktop panel for LXDE project"
 msgstr ""
 
-#: ../src/panel.c:1412
+#: ../src/panel.c:1421
 #, c-format
 msgid "\"%s\" Settings"
 msgstr ""
 
-#: ../src/panel.c:1433
+#: ../src/panel.c:1442
 msgid "Add / Remove Panel Items"
 msgstr ""
 
-#: ../src/panel.c:1441
+#: ../src/panel.c:1450
 #, c-format
 msgid "Remove \"%s\" From Panel"
 msgstr ""
 
-#: ../src/panel.c:1453
+#: ../src/panel.c:1462
 msgid "Panel Settings"
 msgstr ""
 
-#: ../src/panel.c:1459
+#: ../src/panel.c:1468
 msgid "Create New Panel"
 msgstr ""
 
-#: ../src/panel.c:1465
+#: ../src/panel.c:1474
 msgid "Delete This Panel"
 msgstr ""
 
-#: ../src/panel.c:1476
+#: ../src/panel.c:1485
 msgid "About"
 msgstr ""
 
-#: ../src/panel.c:1483
+#: ../src/panel.c:1492
 msgid "Panel"
 msgstr ""
 
-#: ../src/panel.c:1698 ../src/panel.c:1706 ../data/ui/panel-pref.glade.h:22
+#: ../src/panel.c:1708 ../src/panel.c:1716 ../data/ui/panel-pref.glade.h:22
 msgid "Height:"
 msgstr ""
 
-#: ../src/panel.c:1699 ../src/panel.c:1705 ../data/ui/panel-pref.glade.h:21
+#: ../src/panel.c:1709 ../src/panel.c:1715 ../data/ui/panel-pref.glade.h:21
 msgid "Width:"
 msgstr ""
 
-#: ../src/panel.c:1700 ../data/ui/panel-pref.glade.h:13
+#: ../src/panel.c:1710 ../data/ui/panel-pref.glade.h:13
 msgid "Left"
 msgstr ""
 
-#: ../src/panel.c:1701 ../data/ui/panel-pref.glade.h:14
+#: ../src/panel.c:1711 ../data/ui/panel-pref.glade.h:14
 msgid "Right"
 msgstr ""
 
-#: ../src/panel.c:1707 ../data/ui/panel-pref.glade.h:12
+#: ../src/panel.c:1717 ../data/ui/panel-pref.glade.h:12
 msgid "Top"
 msgstr ""
 
-#: ../src/panel.c:1708 ../data/ui/panel-pref.glade.h:11
+#: ../src/panel.c:1718 ../data/ui/panel-pref.glade.h:11
 msgid "Bottom"
 msgstr ""
 
@@ -150,11 +150,11 @@ msgid "No file manager is configured."
 msgstr ""
 
 #. { "configure", N_("Preferences"), configure },
-#: ../src/gtk-run.c:398 ../src/main.c:69 ../plugins/menu.c:718
+#: ../src/gtk-run.c:385 ../src/main.c:69 ../plugins/menu.c:718
 msgid "Run"
 msgstr ""
 
-#: ../src/gtk-run.c:412
+#: ../src/gtk-run.c:399
 msgid "Enter the command you want to execute:"
 msgstr ""
 
@@ -217,44 +217,44 @@ msgid ""
 "\n"
 msgstr ""
 
-#: ../src/input-button.c:145
+#: ../src/input-button.c:147
 msgid "LeftBtn"
 msgstr ""
 
-#: ../src/input-button.c:148
+#: ../src/input-button.c:150
 msgid "MiddleBtn"
 msgstr ""
 
-#: ../src/input-button.c:151
+#: ../src/input-button.c:153
 msgid "RightBtn"
 msgstr ""
 
-#: ../src/input-button.c:154
+#: ../src/input-button.c:156
 #, c-format
 msgid "Btn%s"
 msgstr ""
 
-#: ../src/input-button.c:215
+#: ../src/input-button.c:228
 #, c-format
 msgid "Key combination '%s' cannot be used as a global hotkey, sorry."
 msgstr ""
 
-#: ../src/input-button.c:218 ../src/input-button.c:401
+#: ../src/input-button.c:231 ../src/input-button.c:415
 #: ../plugins/netstatus/netstatus-iface.c:191
 #: ../plugins/netstatus/netstatus-util.c:167
 msgid "Error"
 msgstr ""
 
 #. GtkRadioButton "None"
-#: ../src/input-button.c:315 ../data/ui/panel-pref.glade.h:5
+#: ../src/input-button.c:329 ../data/ui/panel-pref.glade.h:5
 msgid "None"
 msgstr ""
 
-#: ../src/input-button.c:322
+#: ../src/input-button.c:336
 msgid "Custom:"
 msgstr ""
 
-#: ../src/input-button.c:399
+#: ../src/input-button.c:413
 #, c-format
 msgid "Cannot assign '%s' as a global hotkey: it is already bound."
 msgstr ""
@@ -264,7 +264,7 @@ msgid "Spacer"
 msgstr ""
 
 #: ../src/space.c:396 ../data/ui/panel-pref.glade.h:32
-#: ../plugins/batt/batt.c:702
+#: ../plugins/batt/batt.c:710
 msgid "Size"
 msgstr ""
 
@@ -272,7 +272,7 @@ msgstr ""
 msgid "Allocate space"
 msgstr ""
 
-#: ../data/ui/launchtaskbar.glade.h:1 ../plugins/launchtaskbar.c:2417
+#: ../data/ui/launchtaskbar.glade.h:1 ../plugins/launchtaskbar.c:2482
 msgid "Application Launch and Task Bar"
 msgstr ""
 
@@ -472,7 +472,7 @@ msgstr ""
 msgid "Edge:"
 msgstr ""
 
-#: ../data/ui/panel-pref.glade.h:16 ../plugins/volumealsa/volumealsa.c:1291
+#: ../data/ui/panel-pref.glade.h:16 ../plugins/volumealsa/volumealsa.c:1313
 msgid "Center"
 msgstr ""
 
@@ -596,143 +596,143 @@ msgstr ""
 msgid "Advanced"
 msgstr ""
 
-#: ../plugins/cpu/cpu.c:308
+#: ../plugins/cpu/cpu.c:310
 msgid "CPU Usage Monitor"
 msgstr ""
 
-#: ../plugins/cpu/cpu.c:309 ../plugins/monitors/monitors.c:725
+#: ../plugins/cpu/cpu.c:311 ../plugins/monitors/monitors.c:734
 msgid "Display CPU usage"
 msgstr ""
 
-#: ../plugins/deskno/deskno.c:208 ../plugins/deskno/deskno.c:228
+#: ../plugins/deskno/deskno.c:210 ../plugins/deskno/deskno.c:230
 msgid "Desktop Number / Workspace Name"
 msgstr ""
 
-#: ../plugins/deskno/deskno.c:210 ../plugins/dclock.c:437
+#: ../plugins/deskno/deskno.c:212 ../plugins/dclock.c:439
 msgid "Bold font"
 msgstr ""
 
-#: ../plugins/deskno/deskno.c:211
+#: ../plugins/deskno/deskno.c:213
 msgid "Display desktop names"
 msgstr ""
 
-#: ../plugins/deskno/deskno.c:229
+#: ../plugins/deskno/deskno.c:231
 msgid "Display workspace number, by cmeury@users.sf.net"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:1781 ../plugins/launchtaskbar.c:2387
+#: ../plugins/launchtaskbar.c:1831 ../plugins/launchtaskbar.c:2452
 msgid "Application Launch Bar"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:1784 ../plugins/launchtaskbar.c:2397
+#: ../plugins/launchtaskbar.c:1834 ../plugins/launchtaskbar.c:2462
 msgid "Task Bar (Window List)"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:1935
+#: ../plugins/launchtaskbar.c:1985
 msgid "A_dd to Launcher"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:1937
+#: ../plugins/launchtaskbar.c:1987
 msgid "Rem_ove from Launcher"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:1939
+#: ../plugins/launchtaskbar.c:1989
 msgid "_New Instance"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:2388
+#: ../plugins/launchtaskbar.c:2453
 msgid "Bar with buttons to launch application"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:2398
+#: ../plugins/launchtaskbar.c:2463
 msgid ""
 "Taskbar shows all opened windows and allow to iconify them, shade or get "
 "focus"
 msgstr ""
 
-#: ../plugins/launchtaskbar.c:2418
+#: ../plugins/launchtaskbar.c:2483
 msgid "Bar with buttons to launch application and/or show all opened windows"
 msgstr ""
 
 #. Add Raise menu item.
-#: ../plugins/task-button.c:338
+#: ../plugins/task-button.c:352
 msgid "_Raise"
 msgstr ""
 
 #. Add Restore menu item.
-#: ../plugins/task-button.c:343
+#: ../plugins/task-button.c:357
 msgid "R_estore"
 msgstr ""
 
 #. Add Maximize menu item.
-#: ../plugins/task-button.c:348
+#: ../plugins/task-button.c:362
 msgid "Ma_ximize"
 msgstr ""
 
 #. Add Iconify menu item.
-#: ../plugins/task-button.c:353
+#: ../plugins/task-button.c:367
 msgid "Ico_nify"
 msgstr ""
 
-#: ../plugins/task-button.c:374
+#: ../plugins/task-button.c:388
 #, c-format
 msgid "Workspace _%d"
 msgstr ""
 
-#: ../plugins/task-button.c:379
+#: ../plugins/task-button.c:393
 #, c-format
 msgid "Workspace %d"
 msgstr ""
 
 #. Add "move to all workspaces" item.  This causes the window to be visible no matter what desktop is active.
-#: ../plugins/task-button.c:396
+#: ../plugins/task-button.c:410
 msgid "_All workspaces"
 msgstr ""
 
 #. FIXME: add "Current workspace" item, active if not on a current
 #. Add Move to Workspace menu item as a submenu.
-#: ../plugins/task-button.c:404
+#: ../plugins/task-button.c:418
 msgid "_Move to Workspace"
 msgstr ""
 
-#: ../plugins/task-button.c:419
+#: ../plugins/task-button.c:433
 msgid "_Close Window"
 msgstr ""
 
-#: ../plugins/task-button.c:1235
+#: ../plugins/task-button.c:1256
 msgid "_Close all windows"
 msgstr ""
 
-#: ../plugins/dclock.c:431 ../plugins/dclock.c:445
+#: ../plugins/dclock.c:433 ../plugins/dclock.c:453
 msgid "Digital Clock"
 msgstr ""
 
-#: ../plugins/dclock.c:433
+#: ../plugins/dclock.c:435
 msgid "Clock Format"
 msgstr ""
 
-#: ../plugins/dclock.c:434
+#: ../plugins/dclock.c:436
 msgid "Tooltip Format"
 msgstr ""
 
-#: ../plugins/dclock.c:435
+#: ../plugins/dclock.c:437
 #, c-format
 msgid "Format codes: man 3 strftime; %n for line break"
 msgstr ""
 
-#: ../plugins/dclock.c:436
+#: ../plugins/dclock.c:438
 msgid "Action when clicked (default: display calendar)"
 msgstr ""
 
-#: ../plugins/dclock.c:438
+#: ../plugins/dclock.c:440
 msgid "Tooltip only"
 msgstr ""
 
-#: ../plugins/dclock.c:439
+#: ../plugins/dclock.c:441
 msgid "Center text"
 msgstr ""
 
-#: ../plugins/dclock.c:446
+#: ../plugins/dclock.c:454
 msgid "Display digital clock and tooltip"
 msgstr ""
 
@@ -764,15 +764,15 @@ msgstr ""
 msgid "Add a separator to the panel"
 msgstr ""
 
-#: ../plugins/pager.c:114
+#: ../plugins/pager.c:141
 msgid "Sorry, there was no window manager configuration program found."
 msgstr ""
 
-#: ../plugins/pager.c:166 ../plugins/pager.c:182
+#: ../plugins/pager.c:193 ../plugins/pager.c:209
 msgid "Desktop Pager"
 msgstr ""
 
-#: ../plugins/pager.c:167 ../plugins/pager.c:183
+#: ../plugins/pager.c:194 ../plugins/pager.c:210
 msgid "Simple pager plugin"
 msgstr ""
 
@@ -880,19 +880,19 @@ msgstr ""
 msgid "Handle keyboard layouts"
 msgstr ""
 
-#: ../plugins/wincmd.c:205
+#: ../plugins/wincmd.c:207
 msgid "Left click to iconify all windows.  Middle click to shade them."
 msgstr ""
 
-#: ../plugins/wincmd.c:243 ../plugins/wincmd.c:253
+#: ../plugins/wincmd.c:245 ../plugins/wincmd.c:255
 msgid "Minimize All Windows"
 msgstr ""
 
-#: ../plugins/wincmd.c:245
+#: ../plugins/wincmd.c:247
 msgid "Alternately iconify/shade and raise"
 msgstr ""
 
-#: ../plugins/wincmd.c:254
+#: ../plugins/wincmd.c:256
 msgid ""
 "Sends commands to all desktop windows.\n"
 "Supported commands are 1) iconify and 2) shade"
@@ -918,46 +918,46 @@ msgstr ""
 msgid "Browse directory tree via menu (Author = PCMan)"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:564 ../plugins/thermal/thermal.c:582
+#: ../plugins/thermal/thermal.c:563 ../plugins/thermal/thermal.c:581
 msgid "Temperature Monitor"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:566
+#: ../plugins/thermal/thermal.c:565
 msgid "Normal color"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:567
+#: ../plugins/thermal/thermal.c:566
 msgid "Warning1 color"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:568
+#: ../plugins/thermal/thermal.c:567
 msgid "Warning2 color"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:569
+#: ../plugins/thermal/thermal.c:568
 msgid "Automatic sensor location"
 msgstr ""
 
 #. FIXME: if off, disable next one
-#: ../plugins/thermal/thermal.c:570
+#: ../plugins/thermal/thermal.c:569
 msgid "Sensor"
 msgstr ""
 
 #. FIXME: create a list to select instead
-#: ../plugins/thermal/thermal.c:571
+#: ../plugins/thermal/thermal.c:570
 msgid "Automatic temperature levels"
 msgstr ""
 
 #. FIXME: if off, disable two below
-#: ../plugins/thermal/thermal.c:572
+#: ../plugins/thermal/thermal.c:571
 msgid "Warning1 temperature"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:573
+#: ../plugins/thermal/thermal.c:572
 msgid "Warning2 temperature"
 msgstr ""
 
-#: ../plugins/thermal/thermal.c:583
+#: ../plugins/thermal/thermal.c:582
 msgid "Display system temperature"
 msgstr ""
 
@@ -981,114 +981,114 @@ msgid "ALSA (or pulseaudio) had a problem. Please check the lxpanel logs."
 msgstr ""
 
 #. Display current level in tooltip.
-#: ../plugins/volumealsa/volumealsa.c:597
-#: ../plugins/volumealsa/volumealsa.c:952
+#: ../plugins/volumealsa/volumealsa.c:613
+#: ../plugins/volumealsa/volumealsa.c:973
 msgid "Volume control"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:680
+#: ../plugins/volumealsa/volumealsa.c:701
 msgid ""
 "Error, you need to install an application to configure the sound "
 "(pavucontrol, alsamixer ...)"
 msgstr ""
 
 #. Create a frame as the child of the viewport.
-#: ../plugins/volumealsa/volumealsa.c:874
+#: ../plugins/volumealsa/volumealsa.c:895
 msgid "Volume"
 msgstr ""
 
 #. Create a check button as the child of the vertical box.
-#: ../plugins/volumealsa/volumealsa.c:893
+#: ../plugins/volumealsa/volumealsa.c:914
 msgid "Mute"
 msgstr ""
 
 #. Just to have these translated
-#: ../plugins/volumealsa/volumealsa.c:1291
+#: ../plugins/volumealsa/volumealsa.c:1313
 msgid "Line"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1291
+#: ../plugins/volumealsa/volumealsa.c:1313
 msgid "LineOut"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1291
+#: ../plugins/volumealsa/volumealsa.c:1313
 msgid "Front"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1291
+#: ../plugins/volumealsa/volumealsa.c:1313
 msgid "Surround"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1291
+#: ../plugins/volumealsa/volumealsa.c:1313
 msgid "Speaker+LO"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1330
+#: ../plugins/volumealsa/volumealsa.c:1352
 msgid "default"
 msgstr ""
 
 #. desc, index
-#: ../plugins/volumealsa/volumealsa.c:1390
+#: ../plugins/volumealsa/volumealsa.c:1412
 msgid "Master"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1394
+#: ../plugins/volumealsa/volumealsa.c:1416
 msgid "PCM"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1398
+#: ../plugins/volumealsa/volumealsa.c:1420
 msgid "Headphone"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1420
+#: ../plugins/volumealsa/volumealsa.c:1442
 msgid "Click for Volume Slider"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1424
+#: ../plugins/volumealsa/volumealsa.c:1446
 msgid "Click for Toggle Mute"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1428
+#: ../plugins/volumealsa/volumealsa.c:1450
 msgid "Click for Open Mixer"
 msgstr ""
 
 #. setup hotkeys
-#: ../plugins/volumealsa/volumealsa.c:1432
+#: ../plugins/volumealsa/volumealsa.c:1454
 msgid "Hotkey for Volume Up"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1434
+#: ../plugins/volumealsa/volumealsa.c:1456
 msgid "Hotkey for Volume Down"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1436
+#: ../plugins/volumealsa/volumealsa.c:1458
 msgid "Hotkey for Volume Mute"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1492
-#: ../plugins/volumealsa/volumealsa.c:1531
-#: ../plugins/volumealsa/volumealsa.c:1552
+#: ../plugins/volumealsa/volumealsa.c:1514
+#: ../plugins/volumealsa/volumealsa.c:1553
+#: ../plugins/volumealsa/volumealsa.c:1574
 msgid "Volume Control"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1494
+#: ../plugins/volumealsa/volumealsa.c:1516
 msgid "Audio Card"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1497
+#: ../plugins/volumealsa/volumealsa.c:1519
 msgid "Channel to Operate"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1505
+#: ../plugins/volumealsa/volumealsa.c:1527
 msgid "Command to Open Mixer"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1520
+#: ../plugins/volumealsa/volumealsa.c:1542
 msgid "Launch Mixer"
 msgstr ""
 
-#: ../plugins/volumealsa/volumealsa.c:1532
-#: ../plugins/volumealsa/volumealsa.c:1553
+#: ../plugins/volumealsa/volumealsa.c:1554
+#: ../plugins/volumealsa/volumealsa.c:1575
 msgid "Display and control volume"
 msgstr ""
 
@@ -1298,7 +1298,7 @@ msgid "The interface name"
 msgstr ""
 
 #: ../plugins/netstatus/netstatus-iface.c:156
-#: ../plugins/weather/weatherwidget.c:2080
+#: ../plugins/weather/weatherwidget.c:2136
 msgid "State"
 msgstr ""
 
@@ -1536,141 +1536,141 @@ msgstr ""
 msgid "Sending/Receiving"
 msgstr ""
 
-#: ../plugins/batt/batt.c:154
+#: ../plugins/batt/batt.c:155
 #, c-format
 msgid "Battery %d: %d%% charged, %d:%02d until full"
 msgstr ""
 
-#: ../plugins/batt/batt.c:165
+#: ../plugins/batt/batt.c:169
 #, c-format
 msgid "Battery %d: %d%% charged, %d:%02d left"
 msgstr ""
 
-#: ../plugins/batt/batt.c:171
+#: ../plugins/batt/batt.c:176
 #, c-format
 msgid "Battery %d: %d%% charged"
 msgstr ""
 
-#: ../plugins/batt/batt.c:181
+#: ../plugins/batt/batt.c:186
 #, c-format
 msgid ""
 "\n"
 "%sEnergy full design:\t\t%5d mWh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:183
+#: ../plugins/batt/batt.c:188
 #, c-format
 msgid ""
 "\n"
 "%sEnergy full:\t\t\t%5d mWh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:185
+#: ../plugins/batt/batt.c:190
 #, c-format
 msgid ""
 "\n"
 "%sEnergy now:\t\t\t%5d mWh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:187
+#: ../plugins/batt/batt.c:192
 #, c-format
 msgid ""
 "\n"
 "%sPower now:\t\t\t%5d mW"
 msgstr ""
 
-#: ../plugins/batt/batt.c:190
+#: ../plugins/batt/batt.c:195
 #, c-format
 msgid ""
 "\n"
 "%sCharge full design:\t%5d mAh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:192
+#: ../plugins/batt/batt.c:197
 #, c-format
 msgid ""
 "\n"
 "%sCharge full:\t\t\t%5d mAh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:194
+#: ../plugins/batt/batt.c:199
 #, c-format
 msgid ""
 "\n"
 "%sCharge now:\t\t\t%5d mAh"
 msgstr ""
 
-#: ../plugins/batt/batt.c:196
+#: ../plugins/batt/batt.c:201
 #, c-format
 msgid ""
 "\n"
 "%sCurrent now:\t\t\t%5d mA"
 msgstr ""
 
-#: ../plugins/batt/batt.c:199
+#: ../plugins/batt/batt.c:204
 #, c-format
 msgid ""
 "\n"
 "%sCurrent Voltage:\t\t%.3lf V"
 msgstr ""
 
-#: ../plugins/batt/batt.c:237
+#: ../plugins/batt/batt.c:242
 msgid "No batteries found"
 msgstr ""
 
-#: ../plugins/batt/batt.c:534 ../plugins/batt/batt.c:667
+#: ../plugins/batt/batt.c:542 ../plugins/batt/batt.c:675
 msgid "Battery low"
 msgstr ""
 
-#: ../plugins/batt/batt.c:690 ../plugins/batt/batt.c:714
+#: ../plugins/batt/batt.c:698 ../plugins/batt/batt.c:722
 msgid "Battery Monitor"
 msgstr ""
 
-#: ../plugins/batt/batt.c:692
+#: ../plugins/batt/batt.c:700
 msgid "Hide if there is no battery"
 msgstr ""
 
-#: ../plugins/batt/batt.c:693
+#: ../plugins/batt/batt.c:701
 msgid "Alarm command"
 msgstr ""
 
-#: ../plugins/batt/batt.c:694
+#: ../plugins/batt/batt.c:702
 msgid "Alarm time (minutes left)"
 msgstr ""
 
-#: ../plugins/batt/batt.c:695
+#: ../plugins/batt/batt.c:703
 msgid "Background color"
 msgstr ""
 
-#: ../plugins/batt/batt.c:696
+#: ../plugins/batt/batt.c:704
 msgid "Charging color 1"
 msgstr ""
 
-#: ../plugins/batt/batt.c:697
+#: ../plugins/batt/batt.c:705
 msgid "Charging color 2"
 msgstr ""
 
-#: ../plugins/batt/batt.c:698
+#: ../plugins/batt/batt.c:706
 msgid "Discharging color 1"
 msgstr ""
 
-#: ../plugins/batt/batt.c:699
+#: ../plugins/batt/batt.c:707
 msgid "Discharging color 2"
 msgstr ""
 
-#: ../plugins/batt/batt.c:700
+#: ../plugins/batt/batt.c:708
 msgid "Border width"
 msgstr ""
 
-#: ../plugins/batt/batt.c:704
+#: ../plugins/batt/batt.c:712
 msgid "Show Extended Information"
 msgstr ""
 
-#: ../plugins/batt/batt.c:705
+#: ../plugins/batt/batt.c:713
 msgid "Number of battery to monitor"
 msgstr ""
 
-#: ../plugins/batt/batt.c:715
+#: ../plugins/batt/batt.c:723
 msgid "Display battery status using ACPI"
 msgstr ""
 
@@ -1731,37 +1731,37 @@ msgstr ""
 msgid "Add indicator applets to the panel"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:281
+#: ../plugins/monitors/monitors.c:285
 #, c-format
 msgid "CPU usage: %.2f%%"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:377
+#: ../plugins/monitors/monitors.c:386
 #, c-format
 msgid "RAM usage: %.1fMB (%.2f%%)"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:723 ../plugins/monitors/monitors.c:808
+#: ../plugins/monitors/monitors.c:732 ../plugins/monitors/monitors.c:821
 msgid "Resource monitors"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:726
+#: ../plugins/monitors/monitors.c:735
 msgid "CPU color"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:727
+#: ../plugins/monitors/monitors.c:736
 msgid "Display RAM usage"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:728
+#: ../plugins/monitors/monitors.c:737
 msgid "RAM color"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:729
+#: ../plugins/monitors/monitors.c:738
 msgid "Action when clicked (default: lxtask)"
 msgstr ""
 
-#: ../plugins/monitors/monitors.c:809
+#: ../plugins/monitors/monitors.c:822
 msgid "Display monitors (CPU, RAM)"
 msgstr ""
 
@@ -1769,15 +1769,15 @@ msgstr ""
 msgid "[N/A]"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:792
+#: ../plugins/weather/weatherwidget.c:807
 msgid "Enter New Location"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:806
+#: ../plugins/weather/weatherwidget.c:821
 msgid "_New Location:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:817
+#: ../plugins/weather/weatherwidget.c:832
 msgid ""
 "Enter the:\n"
 "- city, or\n"
@@ -1786,183 +1786,267 @@ msgid ""
 "for which to retrieve the weather forecast."
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:854
-#: ../plugins/weather/weatherwidget.c:987
+#: ../plugins/weather/weatherwidget.c:869
+#: ../plugins/weather/weatherwidget.c:1002
 msgid "You must specify a location."
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:901
+#: ../plugins/weather/weatherwidget.c:916
 #, c-format
 msgid "Location '%s' not found!"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1060
+#: ../plugins/weather/weatherwidget.c:1075
 msgid "Preferences"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1066
+#: ../plugins/weather/weatherwidget.c:1081
 msgid "Refresh"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1072
+#: ../plugins/weather/weatherwidget.c:1087
 msgid "Quit"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1229
+#: ../plugins/weather/weatherwidget.c:1254
 msgid "Weather Preferences"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1243
+#: ../plugins/weather/weatherwidget.c:1268
 msgid "Current Location"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1247
-#: ../plugins/weather/weatherwidget.c:1524
+#: ../plugins/weather/weatherwidget.c:1272
+#: ../plugins/weather/weatherwidget.c:1574
 msgid "None configured"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1249
-#: ../plugins/weather/weatherwidget.c:1521
+#: ../plugins/weather/weatherwidget.c:1274
+#: ../plugins/weather/weatherwidget.c:1571
 msgid "_Set"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1270
+#: ../plugins/weather/weatherwidget.c:1295
 msgid "Display"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1274
+#: ../plugins/weather/weatherwidget.c:1299
 msgid "Name:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1278
+#: ../plugins/weather/weatherwidget.c:1303
 msgid "Units:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1282
+#: ../plugins/weather/weatherwidget.c:1307
 msgid "_Metric (°C)"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1284
+#: ../plugins/weather/weatherwidget.c:1309
 msgid "_English (°F)"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1319
+#: ../plugins/weather/weatherwidget.c:1344
 msgid "Forecast"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1323
+#: ../plugins/weather/weatherwidget.c:1348
 msgid "Updates:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1327
+#: ../plugins/weather/weatherwidget.c:1352
 msgid "Ma_nual"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1331
+#: ../plugins/weather/weatherwidget.c:1356
 msgid "_Automatic, every"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1352
+#: ../plugins/weather/weatherwidget.c:1377
 msgid "minutes"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1361
+#: ../plugins/weather/weatherwidget.c:1386
 msgid "Source:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1477
+#: ../plugins/weather/weatherwidget.c:1522
 msgid "C_hange"
 msgstr ""
 
 #. Both are available
-#: ../plugins/weather/weatherwidget.c:1565
+#: ../plugins/weather/weatherwidget.c:1613
 #, c-format
 msgid "Current Conditions for %s"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1589
+#: ../plugins/weather/weatherwidget.c:1637
 msgid "Location:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1612
+#: ../plugins/weather/weatherwidget.c:1660
 msgid "Last updated:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1639
+#: ../plugins/weather/weatherwidget.c:1694
 msgid "Feels like:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1664
+#: ../plugins/weather/weatherwidget.c:1720
 msgid "Humidity:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1691
+#: ../plugins/weather/weatherwidget.c:1747
 msgid "Pressure:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1718
+#: ../plugins/weather/weatherwidget.c:1774
 msgid "Visibility:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1746
+#: ../plugins/weather/weatherwidget.c:1802
 msgid "Wind:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1769
+#: ../plugins/weather/weatherwidget.c:1825
 msgid "Sunrise:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1792
+#: ../plugins/weather/weatherwidget.c:1848
 msgid "Sunset:"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1898
-#: ../plugins/weather/weatherwidget.c:2234
+#: ../plugins/weather/weatherwidget.c:1954
+#: ../plugins/weather/weatherwidget.c:2295
 #, c-format
 msgid "Forecast for %s unavailable."
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1907
-#: ../plugins/weather/weatherwidget.c:2239
+#: ../plugins/weather/weatherwidget.c:1963
+#: ../plugins/weather/weatherwidget.c:2300
 #, c-format
 msgid "Location not set."
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:1924
+#: ../plugins/weather/weatherwidget.c:1980
 #, c-format
 msgid "Searching for '%s'..."
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:2050
+#: ../plugins/weather/weatherwidget.c:2106
 #, c-format
 msgid "Location matches for '%s'"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:2070
+#: ../plugins/weather/weatherwidget.c:2126
 msgid "City"
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:2090
+#: ../plugins/weather/weatherwidget.c:2146
 msgid "Country"
 msgstr ""
 
 #. make it nice and pretty
-#: ../plugins/weather/weatherwidget.c:2221
+#: ../plugins/weather/weatherwidget.c:2278
 msgid "Currently in "
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:2223
+#: ../plugins/weather/weatherwidget.c:2281
 msgid "Today: "
 msgstr ""
 
-#: ../plugins/weather/weatherwidget.c:2224
+#: ../plugins/weather/weatherwidget.c:2282
 msgid "Tomorrow: "
 msgstr ""
 
-#: ../plugins/weather/weather.c:332
+#: ../plugins/weather/weather.c:419
 msgid "Weather Plugin"
 msgstr ""
 
-#: ../plugins/weather/weather.c:333
+#: ../plugins/weather/weather.c:420
 msgid "Show weather conditions for a location."
 msgstr ""
+
+#: ../plugins/weather/yahooutil.c:51
+msgid "N"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:52
+msgid "NNE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:53
+msgid "NE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:54
+msgid "ENE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:55
+msgid "E"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:56
+msgid "ESE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:57
+msgid "SE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:58
+msgid "SSE"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:59
+msgid "S"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:60
+msgid "SSW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:61
+msgid "SW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:62
+msgid "WSW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:63
+msgid "W"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:64
+msgid "WNW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:65
+msgid "NW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:66
+msgid "NNW"
+msgstr ""
+
+#: ../plugins/weather/yahooutil.c:1086
+msgid "Yahoo! Weather"
+msgstr ""
+
+#: ../plugins/weather/openweathermap.c:347
+msgid "Mph"
+msgstr ""
+
+#: ../plugins/weather/openweathermap.c:347
+msgid "m/s"
+msgstr ""
+
+#: ../plugins/weather/openweathermap.c:501
+msgid "m"
+msgstr ""
+
+#: ../plugins/weather/openweathermap.c:916
+msgid "OpenWeatherMap"
+msgstr ""