1 /*  Copyright (c) 2003-2014 Xfce Development Team
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  * Boston, MA 02110-1301, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <string.h>
24 #include <sys/stat.h>
25 
26 #include <libxfce4util/libxfce4util.h>
27 #include <libxfce4ui/libxfce4ui.h>
28 #include <xfconf/xfconf.h>
29 
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32 
33 #include "weather-parsers.h"
34 #include "weather-data.h"
35 #include "weather.h"
36 
37 #include "weather-translate.h"
38 #include "weather-summary.h"
39 #include "weather-config.h"
40 #include "weather-icon.h"
41 #include "weather-scrollbox.h"
42 #include "weather-debug.h"
43 
44 #include "weather-config_ui.h"
45 
46 #define XFCEWEATHER_ROOT "weather"
47 #define CACHE_FILE_MAX_AGE (48 * 3600)
48 #define BORDER (8)
49 #define CONN_TIMEOUT (10)        /* connection timeout in seconds */
50 #define CONN_MAX_ATTEMPTS (3)    /* max retry attempts using small interval */
51 #define CONN_RETRY_INTERVAL_SMALL (10)
52 #define CONN_RETRY_INTERVAL_LARGE (10 * 60)
53 
54 /* power saving update interval in seconds used as a precaution to
55    deal with suspend/resume events etc., when nothing needs to be
56    updated earlier: */
57 #define POWERSAVE_UPDATE_INTERVAL (30)
58 
59 /* standard update interval in seconds used as a precaution to deal
60    with suspend/resume events etc., when nothing needs to be updated
61    earlier: */
62 #define UPDATE_INTERVAL (10)
63 
64 #define DATA_AND_UNIT(var, item)                        \
65     value = get_data(conditions, data->units, item,     \
66                      data->round, data->night_time);    \
67     unit = get_unit(data->units, item);                 \
68     var = g_strdup_printf("%s%s%s",                     \
69                           value,                        \
70                           strcmp(unit, "°") ? " " : "", \
71                           unit);                        \
72     g_free(value);
73 
74 #define CACHE_APPEND(str, val)                  \
75     if (val)                                    \
76         g_string_append_printf(out, str, val);
77 
78 #define CACHE_FREE_VARS()                       \
79     g_free(locname);                            \
80     g_free(lat);                                \
81     g_free(lon);                                \
82     g_free(offset);                             \
83     if (keyfile)                                \
84         g_key_file_free(keyfile);
85 
86 #define CACHE_READ_STRING(var, key)                         \
87     var = g_key_file_get_string(keyfile, group, key, NULL); \
88 
89 #define SCHEDULE_WAKEUP_COMPARE(var, reason)        \
90     if (difftime(var, now_t) < diff) {              \
91         data->next_wakeup = var;                    \
92         diff = difftime(data->next_wakeup, now_t);  \
93         data->next_wakeup_reason = reason;          \
94     }
95 
96 
97 gboolean debug_mode = FALSE;
98 
99 
100 static void write_cache_file(plugin_data *data);
101 
102 static void schedule_next_wakeup(plugin_data *data);
103 
104 
105 void
weather_http_queue_request(SoupSession * session,const gchar * uri,SoupSessionCallback callback_func,gpointer user_data)106 weather_http_queue_request(SoupSession *session,
107                            const gchar *uri,
108                            SoupSessionCallback callback_func,
109                            gpointer user_data)
110 {
111     SoupMessage *msg;
112 
113     msg = soup_message_new("GET", uri);
114     soup_session_queue_message(session, msg, callback_func, user_data);
115 }
116 
117 
118 static gchar *
make_label(const plugin_data * data,data_types type)119 make_label(const plugin_data *data,
120            data_types type)
121 {
122     xml_time *conditions;
123     const gchar *lbl, *unit;
124     gchar *str, *value;
125 
126     switch (type) {
127     case TEMPERATURE:
128         /* TRANSLATORS: Keep in sync with labeloptions in weather-config.c */
129         lbl = _("T");
130         break;
131     case PRESSURE:
132         lbl = _("P");
133         break;
134     case WIND_SPEED:
135         lbl = _("WS");
136         break;
137     case WIND_BEAUFORT:
138         lbl = _("WB");
139         break;
140     case WIND_DIRECTION:
141         lbl = _("WD");
142         break;
143     case WIND_DIRECTION_DEG:
144         lbl = _("WD");
145         break;
146     case HUMIDITY:
147         lbl = _("H");
148         break;
149     case DEWPOINT:
150         lbl = _("D");
151         break;
152     case APPARENT_TEMPERATURE:
153         lbl = _("A");
154         break;
155     case CLOUDS_LOW:
156         lbl = _("CL");
157         break;
158     case CLOUDS_MID:
159         lbl = _("CM");
160         break;
161     case CLOUDS_HIGH:
162         lbl = _("CH");
163         break;
164     case CLOUDINESS:
165         lbl = _("C");
166         break;
167     case FOG:
168         lbl = _("F");
169         break;
170     case PRECIPITATION:
171         lbl = _("R");
172         break;
173     default:
174         lbl = "?";
175         break;
176     }
177 
178     /* get current weather conditions */
179     conditions = get_current_conditions(data->weatherdata);
180     unit = get_unit(data->units, type);
181     value = get_data(conditions, data->units, type,
182                      data->round, data->night_time);
183 
184     if (data->labels->len > 1)
185         str = g_strdup_printf("%s: %s%s%s", lbl, value,
186                               strcmp(unit, "°") || strcmp(unit, "")
187                               ? " " : "", unit);
188     else
189         str = g_strdup_printf("%s%s%s", value,
190                               strcmp(unit, "°") || strcmp(unit, "")
191                               ? " " : "", unit);
192     g_free(value);
193     return str;
194 }
195 
196 
197 static update_info *
make_update_info(const guint check_interval)198 make_update_info(const guint check_interval)
199 {
200     update_info *upi;
201 
202     upi = g_slice_new0(update_info);
203     if (G_UNLIKELY(upi == NULL))
204         return NULL;
205 
206     memset(&upi->last, 0, sizeof(upi->last));
207     upi->next = time(NULL);
208     upi->check_interval = check_interval;
209     return upi;
210 }
211 
212 
213 static void
init_update_infos(plugin_data * data)214 init_update_infos(plugin_data *data)
215 {
216     if (G_LIKELY(data->astro_update))
217         g_slice_free(update_info, data->astro_update);
218     if (G_LIKELY(data->weather_update))
219         g_slice_free(update_info, data->weather_update);
220     if (G_LIKELY(data->conditions_update))
221         g_slice_free(update_info, data->conditions_update);
222 
223     data->astro_update = make_update_info(24 * 3600);
224     data->weather_update = make_update_info(60 * 60);
225     data->conditions_update = make_update_info(5 * 60);
226 }
227 
228 
229 /*
230  * Return the weather plugin cache directory, creating it if
231  * necessary. The string returned does not contain a trailing slash.
232  */
233 gchar *
get_cache_directory(void)234 get_cache_directory(void)
235 {
236     gchar *dir = g_strconcat(g_get_user_cache_dir(), G_DIR_SEPARATOR_S,
237                              "xfce4", G_DIR_SEPARATOR_S, "weather",
238                              NULL);
239     g_mkdir_with_parents(dir, 0755);
240     return dir;
241 }
242 
243 
244 static gint
get_tooltip_icon_size(plugin_data * data)245 get_tooltip_icon_size(plugin_data *data)
246 {
247     switch (data->tooltip_style) {
248     case TOOLTIP_SIMPLE:
249         return 96;
250     case TOOLTIP_VERBOSE:
251     default:
252         return 128;
253     }
254 }
255 
256 
257 void
update_timezone(plugin_data * data)258 update_timezone(plugin_data *data)
259 {
260     if (data->timezone && strlen(data->timezone) > 0)
261         g_setenv("TZ", data->timezone, TRUE);
262     else {
263         if (data->timezone_initial && strlen(data->timezone_initial) > 0)
264             g_setenv("TZ", data->timezone_initial, TRUE);
265         else
266             g_unsetenv("TZ");
267     }
268 }
269 
270 
271 void
update_offset(plugin_data * data)272 update_offset(plugin_data *data)
273 {
274     GDateTime *dt;
275 
276     dt = g_date_time_new_now_local();
277     if (G_LIKELY(data->offset))
278         g_free(data->offset);
279 
280     data->offset = g_date_time_format(dt, "%:z");
281     g_date_time_unref(dt);
282 }
283 
284 
285 void
update_icon(plugin_data * data)286 update_icon(plugin_data *data)
287 {
288     GdkPixbuf *icon;
289     xml_time *conditions;
290     gchar *str;
291     gint size;
292 
293     /* set panel icon according to current weather conditions */
294     size = data->icon_size;
295     conditions = get_current_conditions(data->weatherdata);
296     str = get_data(conditions, data->units, SYMBOL,
297                    data->round, data->night_time);
298     icon = get_icon(data->icon_theme, str, size, data->night_time);
299     gtk_image_set_from_pixbuf(GTK_IMAGE(data->iconimage), icon);
300     if (G_LIKELY(icon))
301         g_object_unref(G_OBJECT(icon));
302 
303     /* set tooltip icon too */
304     size = get_tooltip_icon_size(data);
305     if (G_LIKELY(data->tooltip_icon))
306         g_object_unref(G_OBJECT(data->tooltip_icon));
307     data->tooltip_icon = get_icon(data->icon_theme, str, size, data->night_time);
308     g_free(str);
309     weather_debug("Updated panel and tooltip icons.");
310 }
311 
312 
313 void
scrollbox_set_visible(plugin_data * data)314 scrollbox_set_visible(plugin_data *data)
315 {
316     if (data->show_scrollbox && data->labels->len > 0)
317         gtk_widget_show_all(GTK_WIDGET(data->vbox_center_scrollbox));
318     else
319         gtk_widget_hide(GTK_WIDGET(data->vbox_center_scrollbox));
320     gtk_scrollbox_set_visible(GTK_SCROLLBOX(data->scrollbox),
321                               data->show_scrollbox);
322 }
323 
324 
325 void
update_scrollbox(plugin_data * data,gboolean immediately)326 update_scrollbox(plugin_data *data,
327                  gboolean immediately)
328 {
329     GString *out;
330     gchar *label = NULL;
331     data_types type;
332     guint i = 0, j = 0;
333 
334     gtk_scrollbox_clear_new(GTK_SCROLLBOX(data->scrollbox));
335     if (data->weatherdata && data->weatherdata->current_conditions) {
336         while (i < data->labels->len) {
337             j = 0;
338             out = g_string_sized_new(128);
339             while ((i + j) < data->labels->len && j < data->scrollbox_lines) {
340                 type = g_array_index(data->labels, data_types, i + j);
341                 label = make_label(data, type);
342                 g_string_append_printf(out, "%s%s", label,
343                                        (j < (data->scrollbox_lines - 1) &&
344                                         (i + j + 1) < data->labels->len
345                                         ? "\n"
346                                         : ""));
347                 g_free(label);
348                 j++;
349             }
350             gtk_scrollbox_add_label(GTK_SCROLLBOX(data->scrollbox),
351                                     -1, out->str);
352             g_string_free(out, TRUE);
353             i = i + j;
354         }
355         weather_debug("Added %u labels to scrollbox.", data->labels->len);
356     } else {
357         gtk_scrollbox_add_label(GTK_SCROLLBOX(data->scrollbox), -1,
358                                 _("No Data"));
359         weather_debug("No weather data available, set single label '%s'.",
360                       _("No Data"));
361     }
362 #ifdef HAVE_UPOWER_GLIB
363     if (data->upower_on_battery)
364         gtk_scrollbox_set_animate(GTK_SCROLLBOX(data->scrollbox), FALSE);
365     else
366 #endif
367         gtk_scrollbox_set_animate(GTK_SCROLLBOX(data->scrollbox),
368                                   data->scrollbox_animate);
369     /* update labels immediately (mainly used on config change) */
370     if (immediately) {
371         gtk_scrollbox_prev_label(GTK_SCROLLBOX(data->scrollbox));
372         gtk_scrollbox_swap_labels(GTK_SCROLLBOX(data->scrollbox));
373     }
374     scrollbox_set_visible(data);
375     weather_debug("Updated scrollbox.");
376 }
377 
378 
379 /* get astrodata for the current day */
380 static void
update_current_astrodata(plugin_data * data)381 update_current_astrodata(plugin_data *data)
382 {
383     time_t now_t = time(NULL);
384     gdouble tdiff = -99999;
385 
386     if (G_UNLIKELY(data->astrodata == NULL)) {
387         data->current_astro = NULL;
388         return;
389     }
390 
391     if (data->current_astro)
392         tdiff = difftime(now_t, data->current_astro->day);
393 
394     if (data->current_astro == NULL || tdiff >= 24 * 3600 || tdiff < 0) {
395         data->current_astro = get_astro_data_for_day(data->astrodata, 0);
396         if (G_UNLIKELY(data->current_astro == NULL))
397             weather_debug("No current astrodata available.");
398         else
399             weather_debug("Updated current astrodata.");
400     }
401 }
402 
403 
404 static void
update_current_conditions(plugin_data * data,gboolean immediately)405 update_current_conditions(plugin_data *data,
406                           gboolean immediately)
407 {
408     struct tm now_tm;
409 
410     if (G_UNLIKELY(data->weatherdata == NULL)) {
411         update_icon(data);
412         update_scrollbox(data, TRUE);
413         schedule_next_wakeup(data);
414         return;
415     }
416 
417     if (data->weatherdata->current_conditions) {
418         xml_time_free(data->weatherdata->current_conditions);
419         data->weatherdata->current_conditions = NULL;
420     }
421     /* use exact 5 minute intervals for calculation */
422     time(&data->conditions_update->last);
423     now_tm = *localtime(&data->conditions_update->last);
424     now_tm.tm_min -= (now_tm.tm_min % 5);
425     if (now_tm.tm_min < 0)
426         now_tm.tm_min = 0;
427     now_tm.tm_sec = 0;
428     data->conditions_update->last = mktime(&now_tm);
429 
430     data->weatherdata->current_conditions =
431         make_current_conditions(data->weatherdata,
432                                 data->conditions_update->last);
433 
434     /* update current astrodata */
435     update_current_astrodata(data);
436     data->night_time = is_night_time(data->current_astro);
437 
438     /* update widgets */
439     update_icon(data);
440     update_scrollbox(data, immediately);
441 
442     /* schedule next update */
443     now_tm.tm_min += 5;
444     data->conditions_update->next = mktime(&now_tm);
445     schedule_next_wakeup(data);
446 
447     weather_debug("Updated current conditions.");
448 }
449 
450 
451 static time_t
calc_next_download_time(const update_info * upi,time_t retry_t)452 calc_next_download_time(const update_info *upi,
453                         time_t retry_t) {
454     struct tm retry_tm;
455     guint interval;
456 
457     retry_tm = *localtime(&retry_t);
458 
459     /* If the download failed, retry immediately using a small retry
460      * interval for a limited number of times. If it still fails after
461      * that, continue using a larger interval or the default check,
462      * whatever is smaller.
463      */
464     if (G_LIKELY(upi->attempt == 0))
465         interval = upi->check_interval;
466     else if (upi->attempt <= CONN_MAX_ATTEMPTS)
467         interval = CONN_RETRY_INTERVAL_SMALL;
468     else {
469         if (upi->check_interval > CONN_RETRY_INTERVAL_LARGE)
470             interval = CONN_RETRY_INTERVAL_LARGE;
471         else
472             interval = upi->check_interval;
473     }
474 
475     return time_calc(retry_tm, 0, 0, 0, 0, 0, interval);
476 }
477 
478 
479 /*
480  * Process downloaded astro data and schedule next astro update.
481  */
482 static void
cb_astro_update(SoupSession * session,SoupMessage * msg,gpointer user_data)483 cb_astro_update(SoupSession *session,
484                 SoupMessage *msg,
485                 gpointer user_data)
486 {
487     plugin_data *data = user_data;
488     xmlDoc *doc;
489     xmlNode *root_node, *child_node;
490     time_t now_t;
491     gboolean parsing_error = TRUE;
492 
493     time(&now_t);
494     data->astro_update->attempt++;
495     data->astro_update->http_status_code = msg->status_code;
496     if ((msg->status_code == 200 || msg->status_code == 203)) {
497         doc = get_xml_document(msg);
498         if (G_LIKELY(doc)) {
499             root_node = xmlDocGetRootElement(doc);
500             if (G_LIKELY(root_node)) {
501                 for (child_node = root_node->children; child_node;
502                      child_node = child_node->next) {
503                     if (child_node->type == XML_ELEMENT_NODE) {
504                         if (parse_astrodata(child_node, data->astrodata)) {
505                             /* schedule next update */
506                             data->astro_update->attempt = 0;
507                             data->astro_update->last = now_t;
508                             parsing_error = FALSE;
509                         }
510                     }
511                 }
512             }
513             xmlFreeDoc(doc);
514         }
515         if (parsing_error)
516             g_warning(_("Error parsing astronomical data!"));
517     } else
518         g_warning(_("Download of astronomical data failed with "
519                     "HTTP Status Code %d, Reason phrase: %s"),
520                   msg->status_code, msg->reason_phrase);
521     data->astro_update->next = calc_next_download_time(data->astro_update,
522                                                        now_t);
523 
524     astrodata_clean(data->astrodata);
525     g_array_sort(data->astrodata, (GCompareFunc) xml_astro_compare);
526     update_current_astrodata(data);
527     if (! parsing_error)
528         weather_dump(weather_dump_astrodata, data->astrodata);
529 
530     /* update icon */
531     data->night_time = is_night_time(data->current_astro);
532     update_icon(data);
533 
534     data->astro_update->finished = TRUE;
535 }
536 
537 
538 /*
539  * Process downloaded weather data and schedule next weather update.
540  */
541 static void
cb_weather_update(SoupSession * session,SoupMessage * msg,gpointer user_data)542 cb_weather_update(SoupSession *session,
543                   SoupMessage *msg,
544                   gpointer user_data)
545 {
546     plugin_data *data = user_data;
547     xmlDoc *doc;
548     xmlNode *root_node;
549     time_t now_t;
550     gboolean parsing_error = TRUE;
551 
552     weather_debug("Processing downloaded weather data.");
553     time(&now_t);
554     data->weather_update->attempt++;
555     data->weather_update->http_status_code = msg->status_code;
556     if (msg->status_code == 200 || msg->status_code == 203) {
557         doc = get_xml_document(msg);
558         if (G_LIKELY(doc)) {
559             root_node = xmlDocGetRootElement(doc);
560             if (G_LIKELY(root_node))
561                 if (parse_weather(root_node, data->weatherdata)) {
562                     data->weather_update->attempt = 0;
563                     data->weather_update->last = now_t;
564                     parsing_error = FALSE;
565                 }
566             xmlFreeDoc(doc);
567         }
568         if (parsing_error)
569             g_warning(_("Error parsing weather data!"));
570     } else
571         g_warning
572             (_("Download of weather data failed with HTTP Status Code %d, "
573                "Reason phrase: %s"), msg->status_code, msg->reason_phrase);
574     data->weather_update->next = calc_next_download_time(data->weather_update,
575                                                          now_t);
576 
577     xml_weather_clean(data->weatherdata);
578     g_array_sort(data->weatherdata->timeslices,
579                  (GCompareFunc) xml_time_compare);
580     weather_debug("Updating current conditions.");
581     update_current_conditions(data, !parsing_error);
582     gtk_scrollbox_reset(GTK_SCROLLBOX(data->scrollbox));
583 
584     data->weather_update->finished = TRUE;
585     weather_dump(weather_dump_weatherdata, data->weatherdata);
586 }
587 
588 
589 static gboolean
update_handler(gpointer user_data)590 update_handler(gpointer user_data)
591 {
592     plugin_data *data = user_data;
593     gchar *api_version = FORECAST_API;
594     gchar *url;
595     gboolean night_time;
596     time_t now_t;
597     struct tm now_tm;
598 
599     g_assert(data != NULL);
600     if (G_UNLIKELY(data == NULL))
601         return FALSE;
602 
603     /* plugin has not been configured yet, so simply update icon and
604        scrollbox and return */
605     if (G_UNLIKELY(data->lat == NULL || data->lon == NULL)) {
606         update_icon(data);
607         update_scrollbox(data, TRUE);
608         return FALSE;
609     }
610 
611     now_t = time(NULL);
612     now_tm = *localtime(&now_t);
613 
614     /* check if all started downloads are finished and the cache file
615        can be written */
616     if (data->astro_update->started && data->astro_update->finished &&
617         data->weather_update->started && data->weather_update->finished) {
618         data->astro_update->started = FALSE;
619         data->astro_update->finished = FALSE;
620         data->weather_update->started = FALSE;
621         data->weather_update->finished = FALSE;
622         write_cache_file(data);
623     }
624 
625     /* fetch astronomical data */
626     if (difftime(data->astro_update->next, now_t) <= 0) {
627         /* real next update time will be calculated when update is finished,
628            this is to prevent spawning multiple updates in a row */
629         data->astro_update->next = time_calc_hour(now_tm, 1);
630         data->astro_update->started = TRUE;
631 
632         /* build url */
633         url = g_strdup_printf("https://api.met.no/weatherapi"
634                               "/sunrise/2.0/?lat=%s&lon=%s&"
635                               "date=%04d-%02d-%02d&"
636                               "offset=%s&days=%u",
637                               data->lat, data->lon,
638                               now_tm.tm_year + 1900,
639                               now_tm.tm_mon + 1,
640                               now_tm.tm_mday,
641                               data->offset,
642                               data->forecast_days);
643 
644         /* start receive thread */
645         g_message(_("getting %s"), url);
646         weather_http_queue_request(data->session, url,
647                                    cb_astro_update, data);
648         g_free(url);
649     }
650 
651     /* fetch weather data */
652     if (difftime(data->weather_update->next, now_t) <= 0) {
653         /* real next update time will be calculated when update is finished,
654            this is to prevent spawning multiple updates in a row */
655         data->weather_update->next = time_calc_hour(now_tm, 1);
656         data->weather_update->started = TRUE;
657 
658         /* build url */
659         url = g_strdup_printf("https://api.met.no"
660                               "/weatherapi/locationforecast/%s/"
661                               "classic?lat=%s&lon=%s&altitude=%d",
662                               api_version,
663                               data->lat, data->lon, data->msl);
664 
665         /* start receive thread */
666         g_message(_("getting %s"), url);
667         weather_http_queue_request(data->session, url,
668                                    cb_weather_update, data);
669         g_free(url);
670 
671         /* cb_weather_update will deal with everything that follows this
672          * block, so let's return instead of doing things twice */
673         return FALSE;
674     }
675 
676     /* update current conditions, icon and labels */
677     if (difftime(data->conditions_update->next, now_t) <= 0) {
678         /* real next update time will be calculated when update is finished,
679            this is to prevent spawning multiple updates in a row */
680         data->conditions_update->next = time_calc_hour(now_tm, 1);
681         weather_debug("Updating current conditions.");
682         update_current_conditions(data, FALSE);
683         /* update_current_conditions updates day/night time status
684            too, so quit here */
685         return FALSE;
686     }
687 
688     /* update night time status and icon */
689     update_current_astrodata(data);
690     night_time = is_night_time(data->current_astro);
691     if (data->night_time != night_time) {
692         weather_debug("Night time status changed, updating icon.");
693         data->night_time = night_time;
694         update_icon(data);
695     }
696 
697     schedule_next_wakeup(data);
698     return FALSE;
699 }
700 
701 
702 static void
schedule_next_wakeup(plugin_data * data)703 schedule_next_wakeup(plugin_data *data)
704 {
705     time_t now_t = time(NULL), next_day_t;
706     gdouble diff;
707     gchar *date;
708     GSource *source;
709 
710     if (data->update_timer) {
711         source = g_main_context_find_source_by_id(NULL, data->update_timer);
712         if (source) {
713             g_source_destroy(source);
714             data->update_timer = 0;
715         }
716     }
717 
718     next_day_t = day_at_midnight(now_t, 1);
719     diff = difftime(next_day_t, now_t);
720     data->next_wakeup_reason = "current astro data update";
721     SCHEDULE_WAKEUP_COMPARE(data->astro_update->next,
722                             "astro data download");
723     SCHEDULE_WAKEUP_COMPARE(data->weather_update->next,
724                             "weather data download");
725     SCHEDULE_WAKEUP_COMPARE(data->conditions_update->next,
726                             "current conditions update");
727 
728     /* If astronomical data is unavailable, current conditions update
729        will usually handle night/day. */
730     if (data->current_astro) {
731         if (data->night_time &&
732             difftime(data->current_astro->sunrise, now_t) >= 0)
733             SCHEDULE_WAKEUP_COMPARE(data->current_astro->sunrise,
734                                     "sunrise icon change");
735         if (!data->night_time &&
736             difftime(data->current_astro->sunset, now_t) >= 0)
737             SCHEDULE_WAKEUP_COMPARE(data->current_astro->sunset,
738                                     "sunset icon change");
739     }
740 
741 #ifdef HAVE_UPOWER_GLIB
742     if (data->upower_on_battery && diff > POWERSAVE_UPDATE_INTERVAL) {
743         /* next wakeup time is greater than the power saving check
744            interval, so call the update handler earlier to deal with
745            cases like system resume events etc. */
746         diff = POWERSAVE_UPDATE_INTERVAL;
747         data->next_wakeup_reason = "regular check (power saving)";
748     } else
749 #endif
750     if (diff > UPDATE_INTERVAL) {
751         /* next wakeup time is greater than the standard check
752            interval, so call the update handler earlier to deal with
753            cases like system resume events etc. */
754         diff = UPDATE_INTERVAL;
755         data->next_wakeup_reason = "regular check";
756     } else if (diff < 0) {
757         /* last wakeup time expired, force update immediately */
758         diff = 0;
759         data->next_wakeup_reason = "forced";
760     }
761 
762     date = format_date(now_t, "%Y-%m-%d %H:%M:%S", TRUE);
763     data->update_timer =
764         g_timeout_add_seconds((guint) diff, update_handler, data);
765     if (!strcmp(data->next_wakeup_reason, "regular check"))
766         weather_debug("[%s]: Running regular check for updates, "
767                       "interval %d secs.", date, UPDATE_INTERVAL);
768     else {
769         weather_dump(weather_dump_plugindata, data);
770         weather_debug("[%s]: Next wakeup in %.0f seconds, reason: %s",
771                       date, diff, data->next_wakeup_reason);
772     }
773     g_free(date);
774 }
775 
776 
777 GArray *
labels_clear(GArray * array)778 labels_clear(GArray *array)
779 {
780     if (!array || array->len > 0) {
781         if (array)
782             g_array_free(array, TRUE);
783         array = g_array_new(FALSE, TRUE, sizeof(data_types));
784     }
785     return array;
786 }
787 
788 
789 static void
constrain_to_limits(gint * i,const gint min,const gint max)790 constrain_to_limits(gint *i,
791                     const gint min,
792                     const gint max)
793 {
794     g_assert(i != NULL);
795     if (G_UNLIKELY(i == NULL))
796         return;
797     if (*i < min)
798         *i = min;
799     if (*i > max)
800         *i = max;
801 }
802 
803 
804 static void
constrain_to_ulimits(guint * i,const guint min,const guint max)805 constrain_to_ulimits(guint *i,
806                     const guint min,
807                     const guint max)
808 {
809     g_assert(i != NULL);
810     if (G_UNLIKELY(i == NULL))
811         return;
812     if (*i < min)
813         *i = min;
814     if (*i > max)
815         *i = max;
816 }
817 
818 
819 static void
xfceweather_xfconf_set_intbool(plugin_data * data,gchar * setting,gint value,gboolean is_boolean)820 xfceweather_xfconf_set_intbool (plugin_data *data, gchar* setting, gint value, gboolean is_boolean)
821 {
822     gchar          *property;
823 
824     property = g_strconcat (data->property_base, setting, NULL);
825     if (is_boolean)
826         xfconf_channel_set_bool (data->channel, property, (gboolean) value);
827     else
828         xfconf_channel_set_int (data->channel, property, value);
829     g_free (property);
830 }
831 
832 
833 static void
xfceweather_xfconf_set_string(plugin_data * data,gchar * setting,gchar * value)834 xfceweather_xfconf_set_string (plugin_data *data, gchar* setting, gchar *value)
835 {
836     gchar          *property;
837 
838     property = g_strconcat (data->property_base, setting, NULL);
839     xfconf_channel_set_string (data->channel, property, value);
840     g_free (property);
841 }
842 
843 
844 static gboolean
xfceweather_xfconf_get_bool(plugin_data * data,gchar * setting,gboolean fallback)845 xfceweather_xfconf_get_bool (plugin_data *data, gchar* setting, gboolean fallback)
846 {
847     gchar          *property;
848     gboolean            value;
849 
850     property = g_strconcat (data->property_base, setting, NULL);
851     value = xfconf_channel_get_bool (data->channel, property, fallback);
852     g_free (property);
853 
854     return value;
855 }
856 
857 
858 static gint
xfceweather_xfconf_get_int(plugin_data * data,gchar * setting,gint fallback)859 xfceweather_xfconf_get_int (plugin_data *data, gchar* setting, gint fallback)
860 {
861     gchar          *property;
862     gint            value;
863 
864     property = g_strconcat (data->property_base, setting, NULL);
865     value = xfconf_channel_get_int (data->channel, property, fallback);
866     g_free (property);
867 
868     return value;
869 }
870 
871 
872 static gchar *
xfceweather_xfconf_get_string(plugin_data * data,gchar * setting)873 xfceweather_xfconf_get_string (plugin_data *data, gchar* setting)
874 {
875     gchar          *property, *value;
876 
877     property = g_strconcat (data->property_base, setting, NULL);
878     value = xfconf_channel_get_string (data->channel, property, NULL);
879     g_free (property);
880 
881     return value;
882 }
883 
884 
885 static void
xfceweather_read_config(XfcePanelPlugin * plugin,plugin_data * data)886 xfceweather_read_config (XfcePanelPlugin *plugin,
887                          plugin_data *data)
888 {
889     const gchar *value;
890     gchar *property;
891     gchar label[10];
892     gint label_count = 0, val;
893 
894     g_return_if_fail (XFCONF_IS_CHANNEL (data->channel));
895 
896     data->location_name = xfceweather_xfconf_get_string (data, SETTING_LOCATION_NAME);
897     data->lat = xfceweather_xfconf_get_string (data, SETTING_LATITUDE);
898     data->lon = xfceweather_xfconf_get_string (data, SETTING_LONGITUDE);
899 
900     data->msl = xfceweather_xfconf_get_int (data, SETTING_MSL, 0);
901     constrain_to_limits(&data->msl, -420, 10000);
902     data->timezone = xfceweather_xfconf_get_string (data, SETTING_TIMEZONE);
903     data->offset = xfceweather_xfconf_get_string (data, SETTING_OFFSET);
904     data->geonames_username = xfceweather_xfconf_get_string (data, SETTING_GEONAMES);
905     data->cache_file_max_age = xfceweather_xfconf_get_int (data, SETTING_CACHE_MAX_AGE, CACHE_FILE_MAX_AGE);
906     data->power_saving = xfceweather_xfconf_get_bool (data, SETTING_POWER_SAVING, TRUE);
907 
908     /* Units */
909     if (data->units)
910         g_slice_free(units_config, data->units);
911     data->units = g_slice_new0(units_config);
912     data->units->temperature = xfceweather_xfconf_get_int (data, SETTING_TEMPERATURE, CELSIUS);
913     data->units->pressure = xfceweather_xfconf_get_int (data, SETTING_PRESSURE, HECTOPASCAL);
914     data->units->windspeed = xfceweather_xfconf_get_int (data, SETTING_WINDSPEED, KMH);
915     data->units->precipitation = xfceweather_xfconf_get_int (data, SETTING_PRECIPITATION, MILLIMETERS);
916     data->units->altitude = xfceweather_xfconf_get_int (data, SETTING_ALTITUDE, METERS);
917     data->units->apparent_temperature = xfceweather_xfconf_get_int (data, SETTING_APPARENT_TEMP, STEADMAN);
918 
919     data->round = xfceweather_xfconf_get_bool (data, SETTING_ROUND, TRUE);
920     data->single_row = xfceweather_xfconf_get_bool (data, SETTING_SINGLE_ROW, TRUE);
921     data->tooltip_style = xfceweather_xfconf_get_int (data, SETTING_TOOLTIP_STYLE, TOOLTIP_VERBOSE);
922 
923     /* Forecast */
924     val = xfceweather_xfconf_get_int (data, SETTING_FC_LAYOUT, FC_LAYOUT_LIST);
925     if (val == FC_LAYOUT_CALENDAR || val == FC_LAYOUT_LIST)
926         data->forecast_layout = val;
927     else
928         data->forecast_layout = FC_LAYOUT_LIST;
929 
930     data->forecast_days = xfceweather_xfconf_get_int (data, SETTING_FC_DAYS, DEFAULT_FORECAST_DAYS);
931     constrain_to_ulimits(&data->forecast_days, 1, MAX_FORECAST_DAYS);
932 
933     value = xfceweather_xfconf_get_string (data, SETTING_THEME_DIR);
934     if (data->icon_theme)
935         icon_theme_free(data->icon_theme);
936     data->icon_theme = icon_theme_load(value);
937 
938     /* Scrollbox */
939     data->show_scrollbox = xfceweather_xfconf_get_bool (data, SETTING_SB_SHOW, TRUE);
940     data->scrollbox_use_color = xfceweather_xfconf_get_bool (data, SETTING_SB_USE_COLOR, FALSE);
941     data->scrollbox_lines = xfceweather_xfconf_get_int (data, SETTING_SB_LINES, 1);
942     constrain_to_ulimits(&data->scrollbox_lines, 1, MAX_SCROLLBOX_LINES);
943 
944     value = xfceweather_xfconf_get_string (data, SETTING_SB_FONT);
945     if (value) {
946         g_free (data->scrollbox_font);
947         data->scrollbox_font = g_strdup (value);
948     }
949 
950     value = xfceweather_xfconf_get_string (data, SETTING_SB_COLOR);
951     if (value) {
952         gdk_rgba_parse(&(data->scrollbox_color), value);
953     }
954     data->scrollbox_animate = xfceweather_xfconf_get_bool (data, SETTING_SB_ANIMATE, TRUE);
955     gtk_scrollbox_set_animate (GTK_SCROLLBOX(data->scrollbox),
956                                data->scrollbox_animate);
957 
958     data->labels = labels_clear(data->labels);
959     val = 0;
960     while (val != -1) {
961         g_snprintf(label, 10, "/label%d", label_count++);
962         property = g_strconcat (SETTING_LABELS, label, NULL);
963 
964         val = xfceweather_xfconf_get_int (data, property, -1);
965         if (val >= 0) {
966             g_array_append_val (data->labels, val);
967         }
968         g_free (property);
969     }
970 
971     weather_debug("Config file read.");
972 }
973 
974 static void
xfceweather_write_config(XfcePanelPlugin * plugin,plugin_data * data)975 xfceweather_write_config (XfcePanelPlugin *plugin,
976                           plugin_data *data)
977 {
978     gchar           label[10];
979     guint           i;
980     gchar          *property;
981 
982     g_return_if_fail (XFCONF_IS_CHANNEL (data->channel));
983 
984     if (data->location_name)
985     {
986         xfceweather_xfconf_set_string (data, SETTING_LOCATION_NAME, data->location_name);
987     }
988     if (data->lat)
989     {
990         xfceweather_xfconf_set_string (data, SETTING_LATITUDE, data->lat);
991     }
992     if (data->lon)
993     {
994         xfceweather_xfconf_set_string (data, SETTING_LONGITUDE, data->lon);
995     }
996 
997     xfceweather_xfconf_set_intbool (data, SETTING_MSL, data->msl, FALSE);
998     xfceweather_xfconf_set_string (data, SETTING_TIMEZONE, data->timezone);
999     xfceweather_xfconf_set_string (data, SETTING_OFFSET, data->offset);
1000 
1001     if (data->geonames_username)
1002     {
1003         xfceweather_xfconf_set_string (data, SETTING_GEONAMES, data->geonames_username);
1004     }
1005 
1006     xfceweather_xfconf_set_intbool (data, SETTING_CACHE_MAX_AGE, data->cache_file_max_age, FALSE);
1007     xfceweather_xfconf_set_intbool (data, SETTING_POWER_SAVING, data->power_saving, TRUE);
1008 
1009     xfceweather_xfconf_set_intbool (data, SETTING_TEMPERATURE, data->units->temperature, FALSE);
1010     xfceweather_xfconf_set_intbool (data, SETTING_PRESSURE, data->units->pressure, FALSE);
1011     xfceweather_xfconf_set_intbool (data, SETTING_WINDSPEED, data->units->windspeed, FALSE);
1012     xfceweather_xfconf_set_intbool (data, SETTING_PRECIPITATION, data->units->precipitation, FALSE);
1013     xfceweather_xfconf_set_intbool (data, SETTING_ALTITUDE, data->units->altitude, FALSE);
1014     xfceweather_xfconf_set_intbool (data, SETTING_APPARENT_TEMP, data->units->apparent_temperature, FALSE);
1015 
1016     xfceweather_xfconf_set_intbool (data, SETTING_ROUND, data->round, TRUE);
1017     xfceweather_xfconf_set_intbool (data, SETTING_SINGLE_ROW, data->single_row, TRUE);
1018 
1019     xfceweather_xfconf_set_intbool (data, SETTING_TOOLTIP_STYLE, data->tooltip_style, FALSE);
1020     xfceweather_xfconf_set_intbool (data, SETTING_FC_LAYOUT, data->forecast_layout, FALSE);
1021     xfceweather_xfconf_set_intbool (data, SETTING_FC_DAYS, data->forecast_days, FALSE);
1022 
1023 
1024     if (data->icon_theme && data->icon_theme->dir)
1025     {
1026         xfceweather_xfconf_set_string (data, SETTING_THEME_DIR, data->icon_theme->dir);
1027     }
1028 
1029     /* Scrollbox */
1030     xfceweather_xfconf_set_intbool (data, SETTING_SB_SHOW, data->show_scrollbox, TRUE);
1031     xfceweather_xfconf_set_intbool (data, SETTING_SB_ANIMATE, data->scrollbox_animate, TRUE);
1032     xfceweather_xfconf_set_intbool (data, SETTING_SB_LINES, data->scrollbox_lines, FALSE);
1033     if (data->scrollbox_font)
1034     {
1035       xfceweather_xfconf_set_string (data, SETTING_SB_FONT, data->scrollbox_font);
1036     }
1037     xfceweather_xfconf_set_string (data, SETTING_SB_COLOR, gdk_rgba_to_string(&(data->scrollbox_color)));
1038     xfceweather_xfconf_set_intbool (data, SETTING_SB_USE_COLOR, data->scrollbox_use_color, TRUE);
1039 
1040     /* Labels */
1041     /* Reset all labels prior to storing */
1042     property = g_strconcat (data->property_base, SETTING_LABELS, NULL);
1043     xfconf_channel_reset_property (data->channel, property, TRUE);
1044     g_free (property);
1045 
1046     for (i = 0; i < data->labels->len; i++) {
1047         g_snprintf(label, 10, "/label%d", i);
1048         property = g_strconcat (data->property_base, SETTING_LABELS, label, NULL);
1049         xfconf_channel_set_int (data->channel, property,
1050                                 (gint) g_array_index(data->labels, data_types, i));
1051         g_free (property);
1052     }
1053 
1054     weather_debug("Config written.");
1055 }
1056 
1057 
1058 /*
1059  * Generate file name for the weather data cache file.
1060  */
1061 static gchar *
make_cache_filename(plugin_data * data)1062 make_cache_filename(plugin_data *data)
1063 {
1064     gchar *cache_dir, *file;
1065 
1066     if (G_UNLIKELY(data->lat == NULL || data->lon == NULL))
1067         return NULL;
1068 
1069     cache_dir = get_cache_directory();
1070     file = g_strdup_printf("%s%sweatherdata_%s_%s_%d",
1071                            cache_dir, G_DIR_SEPARATOR_S,
1072                            data->lat, data->lon, data->msl);
1073     g_free(cache_dir);
1074     return file;
1075 }
1076 
1077 
1078 static void
write_cache_file(plugin_data * data)1079 write_cache_file(plugin_data *data)
1080 {
1081     GString *out;
1082     xml_weather *wd = data->weatherdata;
1083     xml_time *timeslice;
1084     xml_location *loc;
1085     xml_astro *astro;
1086     gchar *file, *start, *end, *point, *now, *value;
1087     gchar *date_format = "%Y-%m-%dT%H:%M:%SZ";
1088     time_t now_t = time(NULL);
1089     guint i, j;
1090 
1091     file = make_cache_filename(data);
1092     if (G_UNLIKELY(file == NULL))
1093         return;
1094 
1095     out = g_string_sized_new(20480);
1096     g_string_assign(out, "# xfce4-weather-plugin cache file\n\n[info]\n");
1097     CACHE_APPEND("location_name=%s\n", data->location_name);
1098     CACHE_APPEND("lat=%s\n", data->lat);
1099     CACHE_APPEND("lon=%s\n", data->lon);
1100     CACHE_APPEND("offset=%s\n", data->offset);
1101     g_string_append_printf(out, "msl=%d\n", data->msl);
1102     g_string_append_printf(out, "timeslices=%d\n", wd->timeslices->len);
1103     if (G_LIKELY(data->weather_update)) {
1104         value = format_date(data->weather_update->last, date_format, FALSE);
1105         CACHE_APPEND("last_weather_download=%s\n", value);
1106         g_free(value);
1107     }
1108     if (G_LIKELY(data->astro_update)) {
1109         value = format_date(data->astro_update->last, date_format, FALSE);
1110         CACHE_APPEND("last_astro_download=%s\n", value);
1111         g_free(value);
1112     }
1113     now = format_date(now_t, date_format, FALSE);
1114     CACHE_APPEND("cache_date=%s\n\n", now);
1115     g_free(now);
1116 
1117     if (data->astrodata) {
1118         for (i = 0; i < data->astrodata->len; i++) {
1119             astro = g_array_index(data->astrodata, xml_astro *, i);
1120             if (G_UNLIKELY(astro == NULL))
1121                 continue;
1122             value = format_date(astro->day, "%Y-%m-%d", TRUE);
1123             start = format_date(astro->sunrise, date_format, TRUE);
1124             end = format_date(astro->sunset, date_format, TRUE);
1125             g_string_append_printf(out, "[astrodata%d]\n", i);
1126             CACHE_APPEND("day=%s\n", value);
1127             CACHE_APPEND("sunrise=%s\n", start);
1128             CACHE_APPEND("sunset=%s\n", end);
1129             CACHE_APPEND("sun_never_rises=%s\n",
1130                          astro->sun_never_rises ? "true" : "false");
1131             CACHE_APPEND("sun_never_sets=%s\n",
1132                          astro->sun_never_sets ? "true" : "false");
1133             CACHE_APPEND("solarnoon_elevation=%e\n", astro->solarnoon_elevation);
1134             CACHE_APPEND("solarmidnight_elevation=%e\n", astro->solarmidnight_elevation);
1135             g_free(value);
1136             g_free(start);
1137             g_free(end);
1138 
1139             start = format_date(astro->moonrise, date_format, TRUE);
1140             end = format_date(astro->moonset, date_format, TRUE);
1141             CACHE_APPEND("moonrise=%s\n", start);
1142             CACHE_APPEND("moonset=%s\n", end);
1143             CACHE_APPEND("moon_never_rises=%s\n",
1144                          astro->moon_never_rises ? "true" : "false");
1145             CACHE_APPEND("moon_never_sets=%s\n",
1146                          astro->moon_never_sets ? "true" : "false");
1147             CACHE_APPEND("moon_phase=%s\n", astro->moon_phase);
1148             g_free(start);
1149             g_free(end);
1150 
1151             g_string_append(out, "\n");
1152         }
1153     } else
1154         g_string_append(out, "\n");
1155 
1156     for (i = 0; i < wd->timeslices->len; i++) {
1157         timeslice = g_array_index(wd->timeslices, xml_time *, i);
1158         if (G_UNLIKELY(timeslice == NULL || timeslice->location == NULL))
1159             continue;
1160         loc = timeslice->location;
1161         start = format_date(timeslice->start, date_format, FALSE);
1162         end = format_date(timeslice->end, date_format, FALSE);
1163         point = format_date(timeslice->point, date_format, FALSE);
1164         g_string_append_printf(out, "[timeslice%d]\n", i);
1165         CACHE_APPEND("start=%s\n", start);
1166         CACHE_APPEND("end=%s\n", end);
1167         CACHE_APPEND("point=%s\n", point);
1168         CACHE_APPEND("altitude=%s\n", loc->altitude);
1169         CACHE_APPEND("latitude=%s\n", loc->latitude);
1170         CACHE_APPEND("longitude=%s\n", loc->longitude);
1171         CACHE_APPEND("temperature_value=%s\n", loc->temperature_value);
1172         CACHE_APPEND("temperature_unit=%s\n", loc->temperature_unit);
1173         CACHE_APPEND("wind_dir_deg=%s\n", loc->wind_dir_deg);
1174         CACHE_APPEND("wind_dir_name=%s\n", loc->wind_dir_name);
1175         CACHE_APPEND("wind_speed_mps=%s\n", loc->wind_speed_mps);
1176         CACHE_APPEND("wind_speed_beaufort=%s\n", loc->wind_speed_beaufort);
1177         CACHE_APPEND("humidity_value=%s\n", loc->humidity_value);
1178         CACHE_APPEND("humidity_unit=%s\n", loc->humidity_unit);
1179         CACHE_APPEND("pressure_value=%s\n", loc->pressure_value);
1180         CACHE_APPEND("pressure_unit=%s\n", loc->pressure_unit);
1181         g_free(start);
1182         g_free(end);
1183         g_free(point);
1184         for (j = 0; j < CLOUDS_PERC_NUM; j++)
1185             if (loc->clouds_percent[j])
1186                 g_string_append_printf(out, "clouds_percent_%d=%s\n", j,
1187                                        loc->clouds_percent[j]);
1188         CACHE_APPEND("fog_percent=%s\n", loc->fog_percent);
1189         CACHE_APPEND("precipitation_value=%s\n", loc->precipitation_value);
1190         CACHE_APPEND("precipitation_unit=%s\n", loc->precipitation_unit);
1191         if (loc->symbol)
1192             g_string_append_printf(out, "symbol_id=%d\nsymbol=%s\n",
1193                                    loc->symbol_id, loc->symbol);
1194         g_string_append(out, "\n");
1195     }
1196 
1197     if (!g_file_set_contents(file, out->str, -1, NULL))
1198         g_warning(_("Error writing cache file %s!"), file);
1199     else
1200         weather_debug("Cache file %s has been written.", file);
1201 
1202     g_string_free(out, TRUE);
1203     g_free(file);
1204 }
1205 
1206 
1207 static void
read_cache_file(plugin_data * data)1208 read_cache_file(plugin_data *data)
1209 {
1210     GKeyFile *keyfile;
1211     GError *err = NULL;
1212     xml_weather *wd;
1213     xml_time *timeslice = NULL;
1214     xml_location *loc = NULL;
1215     xml_astro *astro = NULL;
1216     time_t now_t = time(NULL), cache_date_t;
1217     gchar *file, *locname = NULL, *lat = NULL, *lon = NULL, *group = NULL, *offset = NULL;
1218     gchar *timestring;
1219     gint msl, num_timeslices = 0, i, j;
1220 
1221     g_assert(data != NULL);
1222     if (G_UNLIKELY(data == NULL))
1223         return;
1224     wd = data->weatherdata;
1225 
1226     if (G_UNLIKELY(data->lat == NULL || data->lon == NULL))
1227         return;
1228 
1229     file = make_cache_filename(data);
1230     if (G_UNLIKELY(file == NULL))
1231         return;
1232 
1233     keyfile = g_key_file_new();
1234     if (!g_key_file_load_from_file(keyfile, file, G_KEY_FILE_NONE, NULL)) {
1235         weather_debug("Could not read cache file %s.", file);
1236         g_free(file);
1237         return;
1238     }
1239     weather_debug("Reading cache file %s.", file);
1240     g_free(file);
1241 
1242     group = "info";
1243     if (!g_key_file_has_group(keyfile, group)) {
1244         CACHE_FREE_VARS();
1245         return;
1246     }
1247 
1248     /* check all needed values are present and match the current parameters */
1249     locname = g_key_file_get_string(keyfile, group, "location_name", NULL);
1250     lat = g_key_file_get_string(keyfile, group, "lat", NULL);
1251     lon = g_key_file_get_string(keyfile, group, "lon", NULL);
1252     offset = g_key_file_get_string(keyfile, group, "offset", NULL);
1253     if (locname == NULL || lat == NULL || lon == NULL || offset == NULL) {
1254         CACHE_FREE_VARS();
1255         weather_debug("Required values are missing in the cache file, "
1256                       "reading cache file aborted.");
1257         return;
1258     }
1259     msl = g_key_file_get_integer(keyfile, group, "msl", &err);
1260     if (!err)
1261         num_timeslices = g_key_file_get_integer(keyfile, group,
1262                                                 "timeslices", &err);
1263     if (err || strcmp(lat, data->lat) || strcmp(lon, data->lon) ||
1264         strcmp(offset, data->offset) || msl != data->msl ||
1265         num_timeslices < 1) {
1266         CACHE_FREE_VARS();
1267         weather_debug("The required values are not present in the cache file "
1268                       "or do not match the current plugin data. Reading "
1269                       "cache file aborted.");
1270         return;
1271     }
1272     /* read cache creation date and check if cache file is not too old */
1273     CACHE_READ_STRING(timestring, "cache_date");
1274     cache_date_t = parse_timestring(timestring, NULL, FALSE);
1275     g_free(timestring);
1276     if (difftime(now_t, cache_date_t) > data->cache_file_max_age) {
1277         weather_debug("Cache file is too old and will not be used.");
1278         CACHE_FREE_VARS();
1279         return;
1280     }
1281     if (G_LIKELY(data->weather_update)) {
1282         CACHE_READ_STRING(timestring, "last_weather_download");
1283         data->weather_update->last = parse_timestring(timestring, NULL, FALSE);
1284         data->weather_update->next =
1285             calc_next_download_time(data->weather_update,
1286                                     data->weather_update->last);
1287         g_free(timestring);
1288     }
1289     if (G_LIKELY(data->astro_update)) {
1290         CACHE_READ_STRING(timestring, "last_astro_download");
1291         data->astro_update->last = parse_timestring(timestring, NULL, FALSE);
1292         data->astro_update->next =
1293             calc_next_download_time(data->astro_update,
1294                                     data->astro_update->last);
1295         g_free(timestring);
1296     }
1297 
1298     /* read cached astrodata if available and up-to-date */
1299     i = 0;
1300     group = g_strdup_printf("astrodata%d", i);
1301     while (g_key_file_has_group(keyfile, group)) {
1302         if (i == 0)
1303             weather_debug("Reusing cached astrodata instead of downloading it.");
1304 
1305         astro = g_slice_new0(xml_astro);
1306         if (G_UNLIKELY(astro == NULL))
1307             break;
1308 
1309         CACHE_READ_STRING(timestring, "day");
1310         astro->day = parse_timestring(timestring, "%Y-%m-%d", TRUE);
1311         g_free(timestring);
1312         CACHE_READ_STRING(timestring, "sunrise");
1313         astro->sunrise = parse_timestring(timestring, NULL, TRUE);
1314         g_free(timestring);
1315         CACHE_READ_STRING(timestring, "sunset");
1316         astro->sunset = parse_timestring(timestring, NULL, TRUE);
1317         g_free(timestring);
1318         astro->sun_never_rises =
1319             g_key_file_get_boolean(keyfile, group, "sun_never_rises", NULL);
1320         astro->sun_never_sets =
1321             g_key_file_get_boolean(keyfile, group, "sun_never_sets", NULL);
1322         astro->solarnoon_elevation =
1323             g_key_file_get_double(keyfile, group, "solarnoon_elevation", NULL);
1324         astro->solarmidnight_elevation =
1325             g_key_file_get_double(keyfile, group, "solarmidnight_elevation", NULL);
1326 
1327         CACHE_READ_STRING(timestring, "moonrise");
1328         astro->moonrise = parse_timestring(timestring, NULL, TRUE);
1329         g_free(timestring);
1330         CACHE_READ_STRING(timestring, "moonset");
1331         astro->moonset = parse_timestring(timestring, NULL, TRUE);
1332         g_free(timestring);
1333         CACHE_READ_STRING(astro->moon_phase, "moon_phase");
1334         astro->moon_never_rises =
1335             g_key_file_get_boolean(keyfile, group, "moon_never_rises", NULL);
1336         astro->moon_never_sets =
1337             g_key_file_get_boolean(keyfile, group, "moon_never_sets", NULL);
1338 
1339         merge_astro(data->astrodata, astro);
1340         xml_astro_free(astro);
1341 
1342         g_free(group);
1343         group = g_strdup_printf("astrodata%d", ++i);
1344     }
1345     g_free(group);
1346     group = NULL;
1347 
1348     /* parse available timeslices */
1349     for (i = 0; i < num_timeslices; i++) {
1350         group = g_strdup_printf("timeslice%d", i);
1351         if (!g_key_file_has_group(keyfile, group)) {
1352             weather_debug("Group %s not found, continuing with next.", group);
1353             g_free(group);
1354             continue;
1355         }
1356 
1357         timeslice = make_timeslice();
1358         if (G_UNLIKELY(timeslice == NULL)) {
1359             g_free(group);
1360             continue;
1361         }
1362 
1363         /* parse time strings (start, end, point) */
1364         CACHE_READ_STRING(timestring, "start");
1365         timeslice->start = parse_timestring(timestring, NULL, FALSE);
1366         g_free(timestring);
1367         CACHE_READ_STRING(timestring, "end");
1368         timeslice->end = parse_timestring(timestring, NULL, FALSE);
1369         g_free(timestring);
1370         CACHE_READ_STRING(timestring, "point");
1371         timeslice->point = parse_timestring(timestring, NULL, FALSE);
1372         g_free(timestring);
1373 
1374         /* parse location data */
1375         loc = timeslice->location;
1376         CACHE_READ_STRING(loc->altitude, "altitude");
1377         CACHE_READ_STRING(loc->latitude, "latitude");
1378         CACHE_READ_STRING(loc->longitude, "longitude");
1379         CACHE_READ_STRING(loc->temperature_value, "temperature_value");
1380         CACHE_READ_STRING(loc->temperature_unit, "temperature_unit");
1381         CACHE_READ_STRING(loc->wind_dir_name, "wind_dir_name");
1382         CACHE_READ_STRING(loc->wind_dir_deg, "wind_dir_deg");
1383         CACHE_READ_STRING(loc->wind_speed_mps, "wind_speed_mps");
1384         CACHE_READ_STRING(loc->wind_speed_beaufort, "wind_speed_beaufort");
1385         CACHE_READ_STRING(loc->humidity_value, "humidity_value");
1386         CACHE_READ_STRING(loc->humidity_unit, "humidity_unit");
1387         CACHE_READ_STRING(loc->pressure_value, "pressure_value");
1388         CACHE_READ_STRING(loc->pressure_unit, "pressure_unit");
1389 
1390         for (j = 0; j < CLOUDS_PERC_NUM; j++) {
1391             gchar *key = g_strdup_printf("clouds_percent_%d", j);
1392             if (g_key_file_has_key(keyfile, group, key, NULL))
1393                 loc->clouds_percent[j] =
1394                     g_key_file_get_string(keyfile, group, key, NULL);
1395             g_free(key);
1396         }
1397 
1398         CACHE_READ_STRING(loc->fog_percent, "fog_percent");
1399         CACHE_READ_STRING(loc->precipitation_value, "precipitation_value");
1400         CACHE_READ_STRING(loc->precipitation_unit, "precipitation_unit");
1401         CACHE_READ_STRING(loc->symbol, "symbol");
1402         if (loc->symbol &&
1403             g_key_file_has_key(keyfile, group, "symbol_id", NULL))
1404             loc->symbol_id =
1405                 g_key_file_get_integer(keyfile, group, "symbol_id", NULL);
1406 
1407         merge_timeslice(wd, timeslice);
1408         xml_time_free(timeslice);
1409     }
1410     CACHE_FREE_VARS();
1411     weather_debug("Reading cache file complete.");
1412 }
1413 
1414 
1415 void
update_weatherdata_with_reset(plugin_data * data)1416 update_weatherdata_with_reset(plugin_data *data)
1417 {
1418     time_t now_t;
1419     GSource *source;
1420 
1421     weather_debug("Update weatherdata with reset.");
1422     g_assert(data != NULL);
1423     if (G_UNLIKELY(data == NULL))
1424         return;
1425 
1426     if (data->update_timer) {
1427         source = g_main_context_find_source_by_id(NULL, data->update_timer);
1428         if (source) {
1429             g_source_destroy(source);
1430             data->update_timer = 0;
1431         }
1432     }
1433 
1434     /* set location timezone */
1435     update_timezone(data);
1436 
1437     /* set the offset of timezone */
1438     update_offset(data);
1439 
1440     /* clear update times */
1441     init_update_infos(data);
1442 
1443     /* clear existing weather data */
1444     if (data->weatherdata) {
1445         xml_weather_free(data->weatherdata);
1446         data->weatherdata = make_weather_data();
1447     }
1448 
1449     /* clear existing astronomical data */
1450     if (data->astrodata) {
1451         astrodata_free(data->astrodata);
1452         data->astrodata = g_array_sized_new(FALSE, TRUE, sizeof(xml_astro *), 30);
1453     }
1454 
1455     /* update GUI to display NODATA */
1456     update_icon(data);
1457     update_scrollbox(data, TRUE);
1458 
1459     /* make use of previously saved data */
1460     read_cache_file(data);
1461 
1462     /* schedule downloads immediately */
1463     time(&now_t);
1464     data->weather_update->next = now_t;
1465     data->astro_update->next = now_t;
1466     schedule_next_wakeup(data);
1467 
1468     weather_debug("Updated weatherdata with reset.");
1469 }
1470 
1471 
1472 /* This is only a dummy handler, the clicks will be processed by
1473    cb_click. This is needed to synchronise the toggled state with
1474    the existence of the summary window. */
1475 static gboolean
cb_toggled(GtkToggleButton * button,gpointer user_data)1476 cb_toggled(GtkToggleButton *button,
1477            gpointer user_data)
1478 {
1479     plugin_data *data = (plugin_data *) user_data;
1480     g_signal_handlers_block_by_func(data->button, cb_toggled, data);
1481     if (data->summary_window)
1482         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), TRUE);
1483     else
1484         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), FALSE);
1485     g_signal_handlers_unblock_by_func(data->button, cb_toggled, data);
1486     return FALSE;
1487 }
1488 
1489 
1490 static void
close_summary(GtkWidget * widget,gpointer * user_data)1491 close_summary(GtkWidget *widget,
1492               gpointer *user_data)
1493 {
1494     plugin_data *data = (plugin_data *) user_data;
1495     GSource *source;
1496 
1497     if (data->summary_details)
1498         summary_details_free(data->summary_details);
1499     data->summary_details = NULL;
1500     data->summary_window = NULL;
1501 
1502     /* deactivate the summary window update timer */
1503     if (data->summary_update_timer) {
1504         source = g_main_context_find_source_by_id(NULL,
1505                                                   data->summary_update_timer);
1506         if (source) {
1507             g_source_destroy(source);
1508             data->summary_update_timer = 0;
1509         }
1510     }
1511 
1512     /* sync toggle button state */
1513     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), FALSE);
1514 }
1515 
1516 
1517 void
forecast_click(GtkWidget * widget,gpointer user_data)1518 forecast_click(GtkWidget *widget,
1519                gpointer user_data)
1520 {
1521     plugin_data *data = user_data;
1522 
1523     if (data->summary_window != NULL)
1524         gtk_widget_destroy(data->summary_window);
1525     else {
1526         /* sync toggle button state */
1527         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), TRUE);
1528 
1529         data->summary_window = create_summary_window(data);
1530 
1531         /* start the summary window subtitle update timer */
1532         update_summary_subtitle(data);
1533 
1534         g_signal_connect(G_OBJECT(data->summary_window), "destroy",
1535                          G_CALLBACK(close_summary), data);
1536         gtk_widget_show_all(data->summary_window);
1537     }
1538 }
1539 
1540 
1541 static gboolean
cb_click(GtkWidget * widget,GdkEventButton * event,gpointer user_data)1542 cb_click(GtkWidget *widget,
1543          GdkEventButton *event,
1544          gpointer user_data)
1545 {
1546     plugin_data *data = (plugin_data *) user_data;
1547 
1548     if (event->button == 1)
1549         forecast_click(widget, user_data);
1550     else if (event->button == 2)
1551         update_weatherdata_with_reset(data);
1552     return FALSE;
1553 }
1554 
1555 
1556 static gboolean
cb_scroll(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)1557 cb_scroll(GtkWidget *widget,
1558           GdkEventScroll *event,
1559           gpointer user_data)
1560 {
1561     plugin_data *data = (plugin_data *) user_data;
1562 
1563     if (event->direction == GDK_SCROLL_UP)
1564         gtk_scrollbox_next_label(GTK_SCROLLBOX(data->scrollbox));
1565     else if (event->direction == GDK_SCROLL_DOWN)
1566         gtk_scrollbox_prev_label(GTK_SCROLLBOX(data->scrollbox));
1567 
1568     return FALSE;
1569 }
1570 
1571 
1572 static void
mi_click(GtkWidget * widget,gpointer user_data)1573 mi_click(GtkWidget *widget,
1574          gpointer user_data)
1575 {
1576     plugin_data *data = (plugin_data *) user_data;
1577 
1578     update_weatherdata_with_reset(data);
1579 }
1580 
1581 static void
proxy_auth(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)1582 proxy_auth(SoupSession *session,
1583            SoupMessage *msg,
1584            SoupAuth *auth,
1585            gboolean retrying,
1586            gpointer user_data)
1587 {
1588     SoupURI *soup_proxy_uri;
1589     const gchar *proxy_uri;
1590 
1591     if (!retrying) {
1592         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
1593             proxy_uri = g_getenv("HTTP_PROXY");
1594             if (!proxy_uri)
1595                 proxy_uri = g_getenv("http_proxy");
1596             if (proxy_uri) {
1597                 soup_proxy_uri = soup_uri_new(proxy_uri);
1598                 soup_auth_authenticate(auth,
1599                                        soup_uri_get_user(soup_proxy_uri),
1600                                        soup_uri_get_password(soup_proxy_uri));
1601                 soup_uri_free(soup_proxy_uri);
1602             }
1603         }
1604     }
1605 }
1606 
1607 
1608 #ifdef HAVE_UPOWER_GLIB
1609 static void
1610 #if UP_CHECK_VERSION(0, 99, 0)
upower_changed_cb(UpClient * client,GParamSpec * pspec,plugin_data * data)1611 upower_changed_cb(UpClient *client,
1612                   GParamSpec *pspec,
1613                   plugin_data *data)
1614 #else /* UP_CHECK_VERSION < 0.99 */
1615 upower_changed_cb(UpClient *client,
1616                   plugin_data *data)
1617 #endif /* UP_CHECK_VERSION */
1618 {
1619     gboolean on_battery;
1620 
1621     if (G_UNLIKELY(data->upower == NULL) || !data->power_saving)
1622         return;
1623 
1624     on_battery = data->upower_on_battery;
1625     weather_debug("upower old status: on_battery=%d", on_battery);
1626 
1627     data->upower_on_battery = up_client_get_on_battery(client);
1628     weather_debug("upower new status: on_battery=%d", data->upower_on_battery);
1629 
1630     if (data->upower_on_battery != on_battery) {
1631         if (data->summary_window)
1632             update_summary_subtitle(data);
1633 
1634         update_icon(data);
1635         update_scrollbox(data, FALSE);
1636         schedule_next_wakeup(data);
1637     }
1638 }
1639 #endif /* HAVE_UPOWER_GLIB */
1640 
1641 
1642 static void
xfceweather_dialog_response(GtkWidget * dlg,gint response,xfceweather_dialog * dialog)1643 xfceweather_dialog_response(GtkWidget *dlg,
1644                             gint response,
1645                             xfceweather_dialog *dialog)
1646 {
1647     plugin_data *data = (plugin_data *) dialog->pd;
1648     icon_theme *theme;
1649     gboolean result;
1650     guint i;
1651 
1652     if (response == GTK_RESPONSE_HELP) {
1653         /* show help */
1654         result = g_spawn_command_line_async("exo-open --launch WebBrowser "
1655                                             PLUGIN_WEBSITE, NULL);
1656 
1657         if (G_UNLIKELY(result == FALSE))
1658             g_warning(_("Unable to open the following url: %s"),
1659                       PLUGIN_WEBSITE);
1660     } else {
1661         /* free stuff used in config dialog */
1662         gtk_widget_destroy(dlg);
1663         gtk_list_store_clear(dialog->model_datatypes);
1664         for (i = 0; i < dialog->icon_themes->len; i++) {
1665             theme = g_array_index(dialog->icon_themes, icon_theme *, i);
1666             icon_theme_free(theme);
1667         }
1668         g_array_free(dialog->icon_themes, FALSE);
1669         g_slice_free(xfceweather_dialog, dialog);
1670 
1671         xfce_panel_plugin_unblock_menu(data->plugin);
1672 
1673         weather_debug("Write configuration");
1674         xfceweather_write_config(data->plugin, data);
1675         weather_dump(weather_dump_plugindata, data);
1676     }
1677 }
1678 
1679 
1680 static void
xfceweather_create_options(XfcePanelPlugin * plugin,plugin_data * data)1681 xfceweather_create_options(XfcePanelPlugin *plugin,
1682                            plugin_data *data)
1683 {
1684     GtkWidget *dlg;
1685     GtkBuilder *builder;
1686     xfceweather_dialog *dialog;
1687     GError *error = NULL;
1688     gint response;
1689 
1690     xfce_panel_plugin_block_menu(plugin);
1691 
1692     if (xfce_titled_dialog_get_type () == 0)
1693         return;
1694 
1695     builder = gtk_builder_new ();
1696     if (gtk_builder_add_from_string (builder, weather_config_ui,
1697                                      weather_config_ui_length, &error) != 0)
1698     {
1699         dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dialog"));
1700         gtk_window_set_transient_for (GTK_WINDOW (dlg),
1701                                       GTK_WINDOW (gtk_widget_get_toplevel
1702                                        (GTK_WIDGET(plugin))));
1703 
1704         dialog = create_config_dialog(data, builder);
1705 
1706         gtk_widget_show_all (GTK_WIDGET (dlg));
1707         response = gtk_dialog_run(GTK_DIALOG (dlg));
1708         xfceweather_dialog_response(dlg, response, dialog);
1709     } else {
1710         g_warning ("Failed to load dialog: %s", error->message);
1711     }
1712 }
1713 
1714 
1715 static gchar *
weather_get_tooltip_text(const plugin_data * data)1716 weather_get_tooltip_text(const plugin_data *data)
1717 {
1718     xml_time *conditions;
1719     gchar *text, *sym, *alt, *temp;
1720     gchar *windspeed, *windbeau, *winddir, *winddeg;
1721     gchar *pressure, *humidity, *precipitation;
1722     gchar *fog, *cloudiness, *sunval = NULL, *value;
1723     gchar *point, *interval_start, *interval_end, *sunrise, *sunset = NULL;
1724     const gchar *unit;
1725 
1726     conditions = get_current_conditions(data->weatherdata);
1727     if (G_UNLIKELY(conditions == NULL)) {
1728         text = g_strdup(_("Short-term forecast data unavailable."));
1729         return text;
1730     }
1731 
1732     /* times for forecast and point data */
1733     point = format_date(conditions->point, "%H:%M", TRUE);
1734     interval_start = format_date(conditions->start, "%H:%M", TRUE);
1735     interval_end = format_date(conditions->end, "%H:%M", TRUE);
1736 
1737     /* use sunrise and sunset times if available */
1738     if (data->current_astro)
1739         if (data->current_astro->sun_never_rises && data->current_astro->sun_never_sets) {
1740             if (data->current_astro->solarmidnight_elevation > 0)
1741                 sunval = g_strdup(_("The sun never sets today."));
1742             else if (data->current_astro->solarnoon_elevation <= 0)
1743                 sunval = g_strdup(_("The sun never rises today."));
1744         }
1745         else if (data->current_astro->sun_never_rises){
1746             sunset = format_date(data->current_astro->sunset,
1747                                  "%H:%M:%S", FALSE);
1748             sunval =
1749                 g_strdup_printf(_("The sun never rises and sets at %s."),
1750                                  sunset);
1751         }
1752         else if (data->current_astro->sun_never_sets){
1753             sunrise = format_date(data->current_astro->sunrise,
1754                                  "%H:%M:%S", FALSE);
1755             sunval =
1756                 g_strdup_printf(_("The sun rises at %s and never sets."),
1757                                  sunset);
1758         } else {
1759             sunrise = format_date(data->current_astro->sunrise,
1760                                   "%H:%M:%S", TRUE);
1761             sunset = format_date(data->current_astro->sunset,
1762                                  "%H:%M:%S", TRUE);
1763             sunval =
1764                 g_strdup_printf(_("The sun rises at %s and sets at %s."),
1765                                 sunrise, sunset);
1766             g_free(sunrise);
1767             g_free(sunset);
1768         }
1769     else
1770         sunval = g_strdup("");
1771 
1772     sym = get_data(conditions, data->units, SYMBOL, FALSE, data->night_time);
1773     DATA_AND_UNIT(alt, ALTITUDE);
1774     DATA_AND_UNIT(temp, TEMPERATURE);
1775     DATA_AND_UNIT(windspeed, WIND_SPEED);
1776     DATA_AND_UNIT(windbeau, WIND_BEAUFORT);
1777     DATA_AND_UNIT(winddir, WIND_DIRECTION);
1778     DATA_AND_UNIT(winddeg, WIND_DIRECTION_DEG);
1779     DATA_AND_UNIT(pressure, PRESSURE);
1780     DATA_AND_UNIT(humidity, HUMIDITY);
1781     DATA_AND_UNIT(precipitation, PRECIPITATION);
1782     DATA_AND_UNIT(fog, FOG);
1783     DATA_AND_UNIT(cloudiness, CLOUDINESS);
1784 
1785     switch (data->tooltip_style) {
1786     case TOOLTIP_SIMPLE:
1787         text = g_markup_printf_escaped
1788             /*
1789              * TRANSLATORS: This is the simple tooltip. For a bigger challenge,
1790              * look at the verbose tooltip style further below ;-)
1791              */
1792             (_("<b><span size=\"large\">%s</span></b> "
1793                "<span size=\"medium\">(%s)</span>\n"
1794                "<b><span size=\"large\">%s</span></b>\n\n"
1795                "<b>Temperature:</b> %s\n"
1796                "<b>Wind:</b> %s from %s\n"
1797                "<b>Pressure:</b> %s\n"
1798                "<b>Humidity:</b> %s\n"),
1799              data->location_name, alt,
1800              translate_desc(sym, data->night_time),
1801              temp, windspeed, winddir, pressure, humidity);
1802         break;
1803 
1804     case TOOLTIP_VERBOSE:
1805     default:
1806         text = g_markup_printf_escaped
1807             /*
1808              * TRANSLATORS: Re-arrange and align at will, optionally using
1809              * abbreviations for labels if desired or necessary. Just take
1810              * into account the possible size constraints, the centered
1811              * vertical alignment of the icon - which unfortunately cannot
1812              * be changed easily - and try to make it compact and look
1813              * good! The missing space after "%son the ..." is intentional,
1814              * it is included in the %s.
1815              */
1816             (_("<b><span size=\"large\">%s</span></b> "
1817                "<span size=\"medium\">(%s)</span>\n"
1818                "<b><span size=\"large\">%s</span></b>\n"
1819                "<span size=\"smaller\">"
1820                "from %s to %s, with %s of precipitation</span>\n\n"
1821                "<b>Temperature:</b> %s\t\t"
1822                "<span size=\"smaller\">(values at %s)</span>\n"
1823                "<b>Wind:</b> %s (%son the Beaufort scale) from %s(%s)\n"
1824                "<b>Pressure:</b> %s    <b>Humidity:</b> %s\n"
1825                "<b>Fog:</b> %s    <b>Cloudiness:</b> %s\n\n"
1826                "<span size=\"smaller\">%s</span>"),
1827              data->location_name, alt,
1828              translate_desc(sym, data->night_time),
1829              interval_start, interval_end,
1830              precipitation,
1831              temp, point,
1832              windspeed, windbeau, winddir, winddeg,
1833              pressure, humidity,
1834              fog, cloudiness,
1835              sunval);
1836         break;
1837     }
1838     g_free(sunval);
1839     g_free(sym);
1840     g_free(alt);
1841     g_free(temp);
1842     g_free(interval_start);
1843     g_free(interval_end);
1844     g_free(point);
1845     g_free(windspeed);
1846     g_free(windbeau);
1847     g_free(winddir);
1848     g_free(winddeg);
1849     g_free(pressure);
1850     g_free(humidity);
1851     g_free(precipitation);
1852     g_free(fog);
1853     g_free(cloudiness);
1854     return text;
1855 }
1856 
1857 
1858 static gboolean
weather_get_tooltip_cb(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,plugin_data * data)1859 weather_get_tooltip_cb(GtkWidget *widget,
1860                        gint x,
1861                        gint y,
1862                        gboolean keyboard_mode,
1863                        GtkTooltip *tooltip,
1864                        plugin_data *data)
1865 {
1866     gchar *markup_text;
1867 
1868     if (data->weatherdata == NULL)
1869         gtk_tooltip_set_text(tooltip, _("Cannot update weather data"));
1870     else {
1871         markup_text = weather_get_tooltip_text(data);
1872         gtk_tooltip_set_markup(tooltip, markup_text);
1873         g_free(markup_text);
1874     }
1875 
1876     gtk_tooltip_set_icon(tooltip, data->tooltip_icon);
1877     return TRUE;
1878 }
1879 
1880 
1881 static plugin_data *
xfceweather_create_control(XfcePanelPlugin * plugin)1882 xfceweather_create_control(XfcePanelPlugin *plugin)
1883 {
1884     plugin_data *data = g_slice_new0(plugin_data);
1885     SoupURI *soup_proxy_uri;
1886     const gchar *proxy_uri;
1887     const gchar *proxy_user;
1888     GtkWidget *refresh;
1889     GdkPixbuf *icon = NULL;
1890     data_types lbl;
1891 
1892     /* Initialize with sane default values */
1893     data->plugin = plugin;
1894 #ifdef HAVE_UPOWER_GLIB
1895     data->upower = up_client_new();
1896     if (data->upower)
1897         data->upower_on_battery = up_client_get_on_battery(data->upower);
1898 #endif
1899     data->units = g_slice_new0(units_config);
1900     data->weatherdata = make_weather_data();
1901     data->astrodata = g_array_sized_new(FALSE, TRUE, sizeof(xml_astro *), 30);
1902     data->cache_file_max_age = CACHE_FILE_MAX_AGE;
1903     data->show_scrollbox = TRUE;
1904     data->scrollbox_lines = 1;
1905     data->scrollbox_animate = TRUE;
1906     data->tooltip_style = TOOLTIP_VERBOSE;
1907     data->forecast_layout = FC_LAYOUT_LIST;
1908     data->forecast_days = DEFAULT_FORECAST_DAYS;
1909     data->round = TRUE;
1910     data->single_row = TRUE;
1911     data->power_saving = TRUE;
1912 
1913     /* Setup update infos */
1914     init_update_infos(data);
1915     data->next_wakeup = time(NULL);
1916 
1917     /* Setup session for HTTP connections */
1918     data->session = soup_session_new();
1919     g_object_set(data->session, SOUP_SESSION_USER_AGENT,
1920                  PACKAGE_NAME "-" PACKAGE_VERSION, NULL);
1921     g_object_set(data->session, SOUP_SESSION_TIMEOUT,
1922                  CONN_TIMEOUT, NULL);
1923 
1924     /* Set the proxy URI from environment */
1925     proxy_uri = g_getenv("HTTP_PROXY");
1926     if (!proxy_uri)
1927         proxy_uri = g_getenv("http_proxy");
1928     if (proxy_uri) {
1929         soup_proxy_uri = soup_uri_new(proxy_uri);
1930         g_object_set(data->session, SOUP_SESSION_PROXY_URI,
1931                      soup_proxy_uri, NULL);
1932 
1933         /* check if uri contains authentication info */
1934         proxy_user = soup_uri_get_user(soup_proxy_uri);
1935         if (proxy_user && strlen(proxy_user) > 0) {
1936             g_signal_connect(G_OBJECT(data->session), "authenticate",
1937                              G_CALLBACK(proxy_auth), NULL);
1938         }
1939 
1940         soup_uri_free(soup_proxy_uri);
1941     }
1942 
1943     data->scrollbox = gtk_scrollbox_new();
1944 
1945     data->panel_size = xfce_panel_plugin_get_size(plugin);
1946     data->panel_rows = xfce_panel_plugin_get_nrows(plugin);
1947     data->icon_theme = icon_theme_load(NULL);
1948     icon = get_icon(data->icon_theme, NULL, 16, FALSE);
1949     if (G_LIKELY(icon)) {
1950         data->iconimage = gtk_image_new_from_pixbuf(icon);
1951         g_object_unref(G_OBJECT(icon));
1952     } else
1953         g_warning(_("No default icon theme? "
1954                     "This should not happen, plugin will crash!"));
1955 
1956     data->labels = g_array_new(FALSE, TRUE, sizeof(data_types));
1957 
1958     /* create panel toggle button which will contain all other widgets */
1959     data->button = xfce_panel_create_toggle_button();
1960     gtk_container_add(GTK_CONTAINER(plugin), data->button);
1961     xfce_panel_plugin_add_action_widget(plugin, data->button);
1962     gtk_widget_show(data->button);
1963 
1964     /* create alignment box that can be easily adapted to the panel
1965        orientation */
1966     data->alignbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1967     gtk_container_add(GTK_CONTAINER(data->button), data->alignbox);
1968 
1969     /* add widgets to alignment box */
1970     data->vbox_center_scrollbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1971     gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 1);
1972     gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 0.5);
1973     gtk_box_pack_start(GTK_BOX(data->alignbox),
1974                        data->iconimage, TRUE, FALSE, 0);
1975     gtk_box_pack_start(GTK_BOX(data->vbox_center_scrollbox),
1976                        data->scrollbox, TRUE, TRUE, 0);
1977     gtk_box_pack_start(GTK_BOX(data->alignbox),
1978                        data->vbox_center_scrollbox, TRUE, TRUE, 0);
1979     gtk_widget_show_all(data->alignbox);
1980 
1981     /* hook up events for the button */
1982     g_object_set(G_OBJECT(data->button), "has-tooltip", TRUE, NULL);
1983     g_signal_connect(G_OBJECT(data->button), "query-tooltip",
1984                      G_CALLBACK(weather_get_tooltip_cb), data);
1985     g_signal_connect(G_OBJECT(data->button), "button-press-event",
1986                      G_CALLBACK(cb_click), data);
1987     g_signal_connect(G_OBJECT(data->button), "scroll-event",
1988                      G_CALLBACK(cb_scroll), data);
1989     g_signal_connect(G_OBJECT(data->button), "toggled",
1990                      G_CALLBACK(cb_toggled), data);
1991     gtk_widget_add_events(data->scrollbox, GDK_BUTTON_PRESS_MASK);
1992 
1993     /* add refresh button to right click menu, for people who missed
1994        the middle mouse click feature */
1995     refresh = gtk_menu_item_new_with_mnemonic(_("Re_fresh"));
1996     gtk_widget_show(refresh);
1997     g_signal_connect(G_OBJECT(refresh), "activate",
1998                      G_CALLBACK(mi_click), data);
1999     xfce_panel_plugin_menu_insert_item(plugin, GTK_MENU_ITEM(refresh));
2000 
2001     /* assign to tempval because g_array_append_val() is using & operator */
2002     lbl = TEMPERATURE;
2003     g_array_append_val(data->labels, lbl);
2004     lbl = WIND_DIRECTION;
2005     g_array_append_val(data->labels, lbl);
2006     lbl = WIND_SPEED;
2007     g_array_append_val(data->labels, lbl);
2008 
2009     weather_debug("Plugin widgets set up and ready.");
2010     return data;
2011 }
2012 
2013 
2014 static void
xfceweather_free(XfcePanelPlugin * plugin,plugin_data * data)2015 xfceweather_free(XfcePanelPlugin *plugin,
2016                  plugin_data *data)
2017 {
2018     GSource *source;
2019 
2020     weather_debug("Freeing plugin data.");
2021     g_assert(data != NULL);
2022 
2023     if (data->update_timer) {
2024         source = g_main_context_find_source_by_id(NULL, data->update_timer);
2025         if (source) {
2026             g_source_destroy(source);
2027             data->update_timer = 0;
2028         }
2029     }
2030 
2031 #ifdef HAVE_UPOWER_GLIB
2032     if (data->upower) {
2033         g_object_unref(data->upower);
2034         data->upower = NULL;
2035     }
2036 #endif
2037 
2038     if (data->weatherdata)
2039         xml_weather_free(data->weatherdata);
2040 
2041     if (data->units)
2042         g_slice_free(units_config, data->units);
2043 
2044     xmlCleanupParser();
2045 
2046     /* free chars */
2047     g_free(data->lat);
2048     g_free(data->lon);
2049     g_free(data->location_name);
2050     g_free(data->scrollbox_font);
2051     g_free(data->timezone);
2052     g_free(data->offset);
2053     g_free(data->timezone_initial);
2054     g_free(data->geonames_username);
2055 
2056     /* free update infos */
2057     g_slice_free(update_info, data->weather_update);
2058     g_slice_free(update_info, data->astro_update);
2059     g_slice_free(update_info, data->conditions_update);
2060 
2061     /* free current data */
2062     data->current_astro = NULL;
2063 
2064     /* free arrays */
2065     g_array_free(data->labels, TRUE);
2066     astrodata_free(data->astrodata);
2067 
2068     /* free icon theme */
2069     icon_theme_free(data->icon_theme);
2070 
2071     g_slice_free(plugin_data, data);
2072     xfconf_shutdown ();
2073 }
2074 
2075 static gboolean
xfceweather_set_size(XfcePanelPlugin * panel,gint size,plugin_data * data)2076 xfceweather_set_size(XfcePanelPlugin *panel,
2077                      gint size,
2078                      plugin_data *data)
2079 {
2080     gint icon_size;
2081 
2082 #if LIBXFCE4PANEL_CHECK_VERSION(4,9,0)
2083     data->panel_rows = xfce_panel_plugin_get_nrows(panel);
2084     if (data->single_row)
2085         size /= data->panel_rows;
2086 #endif
2087     data->panel_size = size;
2088 
2089 #if LIBXFCE4PANEL_CHECK_VERSION(4, 13, 0)
2090     icon_size = xfce_panel_plugin_get_icon_size (panel);
2091 #else
2092     icon_size = data->panel_size;
2093 
2094     /* make icon smaller when not single-row in multi-row panels */
2095     if (!data->single_row && data->panel_rows > 2)
2096         icon_size *= 0.80;
2097 
2098     /* take into account the border of the toggle button */
2099     icon_size -= 2;
2100 #endif
2101     data->icon_size = icon_size;
2102 
2103     update_icon(data);
2104     update_scrollbox(data, FALSE);
2105 
2106     weather_dump(weather_dump_plugindata, data);
2107 
2108     /* we handled the size */
2109     return TRUE;
2110 }
2111 
2112 
2113 #if LIBXFCE4PANEL_CHECK_VERSION(4,9,0)
2114 gboolean
xfceweather_set_mode(XfcePanelPlugin * panel,XfcePanelPluginMode mode,plugin_data * data)2115 xfceweather_set_mode(XfcePanelPlugin *panel,
2116                      XfcePanelPluginMode mode,
2117                      plugin_data *data)
2118 {
2119     data->panel_orientation = xfce_panel_plugin_get_mode(panel);
2120 
2121     if (data->panel_orientation == XFCE_PANEL_PLUGIN_MODE_HORIZONTAL ||
2122         (data->panel_orientation == XFCE_PANEL_PLUGIN_MODE_DESKBAR &&
2123          data->single_row)) {
2124         gtk_orientable_set_orientation(GTK_ORIENTABLE(data->alignbox),
2125                                    GTK_ORIENTATION_HORIZONTAL);
2126         gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 1);
2127         gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 0.5);
2128     } else {
2129         gtk_orientable_set_orientation(GTK_ORIENTABLE(data->alignbox),
2130                                    GTK_ORIENTATION_VERTICAL);
2131         gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 0.5);
2132         gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 1);
2133     }
2134 
2135     if (mode == XFCE_PANEL_PLUGIN_MODE_DESKBAR)
2136         xfce_panel_plugin_set_small(panel, FALSE);
2137     else
2138         xfce_panel_plugin_set_small(panel, data->single_row);
2139 
2140     gtk_scrollbox_set_orientation(GTK_SCROLLBOX(data->scrollbox),
2141                                   (mode != XFCE_PANEL_PLUGIN_MODE_VERTICAL)
2142                                   ? GTK_ORIENTATION_HORIZONTAL
2143                                   : GTK_ORIENTATION_VERTICAL);
2144 
2145     xfceweather_set_size(panel, xfce_panel_plugin_get_size(panel), data);
2146 
2147     weather_dump(weather_dump_plugindata, data);
2148 
2149     /* we handled the orientation */
2150     return TRUE;
2151 }
2152 
2153 
2154 #else
2155 
2156 
2157 static gboolean
xfceweather_set_orientation(XfcePanelPlugin * panel,GtkOrientation orientation,plugin_data * data)2158 xfceweather_set_orientation(XfcePanelPlugin *panel,
2159                             GtkOrientation orientation,
2160                             plugin_data *data)
2161 {
2162     data->panel_orientation = orientation;
2163 
2164     if (data->panel_orientation == GTK_ORIENTATION_HORIZONTAL) {
2165         gtk_orientable_set_orientation(data->alignbox,
2166                                    GTK_ORIENTATION_HORIZONTAL);
2167         gtk_misc_set_alignment(GTK_MISC(data->iconimage), 1, 0.5);
2168     } else {
2169         gtk_orientable_set_orientation(data->alignbox,
2170                                    GTK_ORIENTATION_VERTICAL);
2171         gtk_misc_set_alignment(GTK_MISC(data->iconimage), 0.5, 1);
2172     }
2173     gtk_scrollbox_set_orientation(GTK_SCROLLBOX(data->scrollbox),
2174                                   data->panel_orientation);
2175 
2176     update_icon(data);
2177     update_scrollbox(data, FALSE);
2178 
2179     weather_dump(weather_dump_plugindata, data);
2180 
2181     /* we handled the orientation */
2182     return TRUE;
2183 }
2184 #endif
2185 
2186 
2187 static void
xfceweather_show_about(XfcePanelPlugin * plugin,plugin_data * data)2188 xfceweather_show_about(XfcePanelPlugin *plugin,
2189                        plugin_data *data)
2190 {
2191     GdkPixbuf *icon;
2192     const gchar *auth[] = {
2193         "Bob Schlärmann <weatherplugin@atreidis.nl.eu.org>",
2194         "Benedikt Meurer <benny@xfce.org>",
2195         "Jasper Huijsmans <jasper@xfce.org>",
2196         "Masse Nicolas <masse_nicolas@yahoo.fr>",
2197         "Nick Schermer <nick@xfce.org>",
2198         "Colin Leroy <colin@colino.net>",
2199         "Harald Judt <h.judt@gmx.at>",
2200         "Simon Steinbeiß <simon@xfce.org>",
2201         NULL };
2202     icon = xfce_panel_pixbuf_from_source("xfce4-weather", NULL, 48);
2203     gtk_show_about_dialog
2204         (NULL,
2205          "logo", icon,
2206          "license", xfce_get_license_text(XFCE_LICENSE_TEXT_GPL),
2207          "version", PACKAGE_VERSION,
2208          "program-name", PACKAGE_NAME,
2209          "comments", _("Show weather conditions and forecasts"),
2210          "website", PLUGIN_WEBSITE,
2211          "copyright", _("Copyright (c) 2003-2021\n"),
2212          "authors", auth,
2213          NULL);
2214 
2215     if (icon)
2216         g_object_unref(G_OBJECT(icon));
2217 }
2218 
2219 
2220 static void
weather_construct(XfcePanelPlugin * plugin)2221 weather_construct(XfcePanelPlugin *plugin)
2222 {
2223     plugin_data *data;
2224     const gchar *panel_debug_env;
2225 
2226     /* Enable debug level logging if PANEL_DEBUG contains G_LOG_DOMAIN */
2227     panel_debug_env = g_getenv("PANEL_DEBUG");
2228     if (panel_debug_env && strstr(panel_debug_env, G_LOG_DOMAIN))
2229         debug_mode = TRUE;
2230     weather_debug_init(G_LOG_DOMAIN, debug_mode);
2231     weather_debug("weather plugin version " VERSION " starting up");
2232 
2233     xfce_textdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");
2234     data = xfceweather_create_control(plugin);
2235 
2236     if (xfconf_init (NULL))
2237         data->channel = xfconf_channel_get ("xfce4-panel");
2238     else
2239     {
2240         g_warning ("Could not initialize xfconf.");
2241         return;
2242     }
2243 
2244     data->property_base = xfce_panel_plugin_get_property_base (plugin);
2245 
2246     /* save initial timezone so we can reset it later */
2247     data->timezone_initial = g_strdup(g_getenv("TZ"));
2248 
2249     data->tooltip_icon = NULL;
2250     xfceweather_read_config(plugin, data);
2251     update_timezone(data);
2252     update_offset(data);
2253     read_cache_file(data);
2254     update_current_conditions(data, TRUE);
2255     scrollbox_set_visible(data);
2256     gtk_scrollbox_set_fontname(GTK_SCROLLBOX(data->scrollbox),
2257                                data->scrollbox_font);
2258     if (data->scrollbox_use_color)
2259         gtk_scrollbox_set_color(GTK_SCROLLBOX(data->scrollbox),
2260                                 data->scrollbox_color);
2261 
2262 #if LIBXFCE4PANEL_CHECK_VERSION(4,9,0)
2263     xfceweather_set_mode(plugin, xfce_panel_plugin_get_mode(plugin), data);
2264 #else
2265     xfceweather_set_orientation(plugin, xfce_panel_plugin_get_orientation(plugin), data);
2266 #endif
2267     xfceweather_set_size(plugin, data->panel_size, data);
2268 
2269     g_signal_connect(G_OBJECT(plugin), "free-data",
2270                      G_CALLBACK(xfceweather_free), data);
2271     g_signal_connect(G_OBJECT(plugin), "save",
2272                      G_CALLBACK(xfceweather_write_config), data);
2273     g_signal_connect(G_OBJECT(plugin), "size-changed",
2274                      G_CALLBACK(xfceweather_set_size), data);
2275 #if LIBXFCE4PANEL_CHECK_VERSION(4,9,0)
2276     g_signal_connect(G_OBJECT(plugin), "mode-changed",
2277                      G_CALLBACK(xfceweather_set_mode), data);
2278 #else
2279     g_signal_connect(G_OBJECT(plugin), "orientation-changed",
2280                      G_CALLBACK(xfceweather_set_orientation), data);
2281 #endif
2282     xfce_panel_plugin_menu_show_configure(plugin);
2283     g_signal_connect(G_OBJECT(plugin), "configure-plugin",
2284                      G_CALLBACK(xfceweather_create_options), data);
2285 
2286     xfce_panel_plugin_menu_show_about(plugin);
2287     g_signal_connect(G_OBJECT(plugin), "about",
2288                      G_CALLBACK(xfceweather_show_about), data);
2289 
2290 #ifdef HAVE_UPOWER_GLIB
2291     if (data->upower) {
2292 #if UP_CHECK_VERSION(0, 99, 0)
2293         g_signal_connect (data->upower, "notify",
2294                           G_CALLBACK(upower_changed_cb), data);
2295 #else /* UP_CHECK_VERSION < 0.99 */
2296         g_signal_connect (data->upower, "changed",
2297                           G_CALLBACK(upower_changed_cb), data);
2298 #endif /* UP_CHECK_VERSION */
2299     }
2300 #endif /* HAVE_UPOWER_GLIB */
2301 
2302     weather_dump(weather_dump_plugindata, data);
2303 }
2304 
2305 XFCE_PANEL_PLUGIN_REGISTER(weather_construct)
2306