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