1 /* -*- mode: C; c-file-style: "linux"; indent-tabs-mode: t -*-
2  * gnome-wall-clock.h - monitors TZ setting files and signals changes
3  *
4  * Copyright (C) 2010 Red Hat, Inc.
5  * Copyright (C) 2011 Red Hat, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public License
9  * as published by the Free Software Foundation; either version 2 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  * 02110-1301, USA.
21  *
22  * Author: Colin Walters <walters@verbum.org>
23  */
24 
25 #include "config.h"
26 
27 #include <glib/gi18n-lib.h>
28 
29 #define GNOME_DESKTOP_USE_UNSTABLE_API
30 #include "gnome-wall-clock.h"
31 #include "gnome-datetime-source.h"
32 
33 typedef enum
34 {
35   INTERVAL_SECOND,
36   INTERVAL_MINUTE,
37 } ClockInterval;
38 
39 struct _GnomeWallClockPrivate {
40 	guint clock_update_id;
41 
42     char *clock_string;
43 
44     const char *default_time_format;
45     const char *default_date_format;
46     char *format_string;
47     gboolean custom_format;
48 
49 	GFileMonitor *tz_monitor;
50 	GSettings    *desktop_settings;
51 
52     ClockInterval update_interval;
53 };
54 
55 enum {
56 	PROP_0,
57 	PROP_CLOCK,
58 	PROP_FORMAT_STRING,
59 };
60 
61 G_DEFINE_TYPE (GnomeWallClock, gnome_wall_clock, G_TYPE_OBJECT);
62 
63 /* Date/Time format defaults - options are stored in org.cinnamon.desktop.interface keys.
64  * The wall clock is used variously in Cinnamon applets and desklets, as well as
65  * cinnamon-screensaver's default lock screen. */
66 
67 /* Default date format (typically matching date portion of WITH_DATE_* defaults.)
68  * Currently used by cinnamon-screensaver default clock */
69 #define DATE_ONLY             (_("%A, %B %e"))
70 
71 /* Defaut date/time format when show-date, show-seconds, use-24h are set */
72 #define WITH_DATE_24H_SECONDS (_("%A %B %e, %R:%S"))
73 
74 /* Default date/time format when show-date, show-seconds are set */
75 #define WITH_DATE_12H_SECONDS (_("%A %B %e, %l:%M:%S %p"))
76 
77 /* Default date/time format when show-date, use-24h are set */
78 #define WITH_DATE_24H         (_("%A %B %e, %R"))
79 
80 /* Default date/time format when just show-date is set */
81 #define WITH_DATE_12H         (_("%A %B %e, %l:%M %p"))
82 
83 /* Default date/time format when show-seconds, use-24h are set */
84 #define NO_DATE_24H_SECONDS   (_("%R:%S"))
85 
86 /* Default date/time format when just show-seconds is set */
87 #define NO_DATE_12H_SECONDS   (_("%l:%M:%S %p"))
88 
89 /* Default date/time format when just use-24h is set */
90 #define NO_DATE_24H           (_("%R"))
91 
92 /* Default date/time format with no options are set */
93 #define NO_DATE_12H           (_("%l:%M %p"))
94 
95 #define NO_DATE               ("")
96 
97 /************/
98 
99 static void update_format_string (GnomeWallClock *self, const gchar *format_string);
100 static gboolean update_clock (gpointer data);
101 static void on_schema_change (GSettings *schema,
102                               const char *key,
103                               gpointer user_data);
104 static void on_tz_changed (GFileMonitor *monitor,
105                            GFile        *file,
106                            GFile        *other_file,
107                            GFileMonitorEvent *event,
108                            gpointer      user_data);
109 
110 static void
gnome_wall_clock_init(GnomeWallClock * self)111 gnome_wall_clock_init (GnomeWallClock *self)
112 {
113 	GFile *tz;
114 
115 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_WALL_CLOCK, GnomeWallClockPrivate);
116 
117 	self->priv->clock_string = NULL;
118 
119 	tz = g_file_new_for_path ("/etc/localtime");
120 	self->priv->tz_monitor = g_file_monitor_file (tz, 0, NULL, NULL);
121 	g_object_unref (tz);
122 
123 	g_signal_connect (self->priv->tz_monitor, "changed", G_CALLBACK (on_tz_changed), self);
124 
125 	self->priv->desktop_settings = g_settings_new ("org.cinnamon.desktop.interface");
126 	g_signal_connect (self->priv->desktop_settings, "changed", G_CALLBACK (on_schema_change), self);
127 
128      /* A format string provided for construction will be set after gnome_wall_clock_init()
129       * finishes.  If not provided, our internal format and interval will still be set to
130       * some default by this. */
131     gnome_wall_clock_set_format_string (self, NULL);
132 }
133 
134 static void
gnome_wall_clock_dispose(GObject * object)135 gnome_wall_clock_dispose (GObject *object)
136 {
137 	GnomeWallClock *self = GNOME_WALL_CLOCK (object);
138 
139 	if (self->priv->clock_update_id) {
140 		g_source_remove (self->priv->clock_update_id);
141 		self->priv->clock_update_id = 0;
142 	}
143 
144 	if (self->priv->tz_monitor != NULL) {
145 		g_object_unref (self->priv->tz_monitor);
146 		self->priv->tz_monitor = NULL;
147 	}
148 
149 	if (self->priv->desktop_settings != NULL) {
150 		g_object_unref (self->priv->desktop_settings);
151 		self->priv->desktop_settings = NULL;
152 	}
153 
154 	G_OBJECT_CLASS (gnome_wall_clock_parent_class)->dispose (object);
155 }
156 
157 static void
gnome_wall_clock_finalize(GObject * object)158 gnome_wall_clock_finalize (GObject *object)
159 {
160 	GnomeWallClock *self = GNOME_WALL_CLOCK (object);
161 
162     g_clear_pointer (&self->priv->clock_string, g_free);
163     g_clear_pointer (&self->priv->format_string, g_free);
164 
165 	G_OBJECT_CLASS (gnome_wall_clock_parent_class)->finalize (object);
166 }
167 
168 static void
gnome_wall_clock_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)169 gnome_wall_clock_get_property (GObject    *gobject,
170 			       guint       prop_id,
171 			       GValue     *value,
172 			       GParamSpec *pspec)
173 {
174 	GnomeWallClock *self = GNOME_WALL_CLOCK (gobject);
175 
176 	switch (prop_id)
177 	{
178 	case PROP_CLOCK:
179 		g_value_set_string (value, self->priv->clock_string);
180 		break;
181     case PROP_FORMAT_STRING:
182         g_value_set_string (value, self->priv->format_string);
183         break;
184 	default:
185 		G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
186 		break;
187 	}
188 }
189 
190 static void
gnome_wall_clock_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)191 gnome_wall_clock_set_property (GObject      *gobject,
192 			       guint         prop_id,
193 			       const GValue *value,
194 			       GParamSpec   *pspec)
195 {
196 	GnomeWallClock *self = GNOME_WALL_CLOCK (gobject);
197 
198 	switch (prop_id)
199 	{
200     case PROP_FORMAT_STRING:
201         gnome_wall_clock_set_format_string (self, g_value_get_string (value));
202 		break;
203 	default:
204 		G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
205 		break;
206 	}
207 }
208 
209 
210 static void
gnome_wall_clock_class_init(GnomeWallClockClass * klass)211 gnome_wall_clock_class_init (GnomeWallClockClass *klass)
212 {
213 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
214 
215 	gobject_class->get_property = gnome_wall_clock_get_property;
216 	gobject_class->set_property = gnome_wall_clock_set_property;
217 	gobject_class->dispose = gnome_wall_clock_dispose;
218 	gobject_class->finalize = gnome_wall_clock_finalize;
219 
220 	/**
221 	 * GnomeWallClock:clock:
222 	 *
223 	 * A formatted string representing the current clock display.
224 	 */
225 	g_object_class_install_property (gobject_class,
226 					 PROP_CLOCK,
227 					 g_param_spec_string ("clock",
228 							      "",
229 							      "",
230 							      NULL,
231 							      G_PARAM_READABLE));
232 
233 	/**
234 	 * GnomeWallClock:format-string:
235 	 *
236 	 * If not NULL, the wall clock will format the time/date according to
237      * this format string.  If the format string is invalid, the default string
238      * will be used instead.
239 	 */
240     g_object_class_install_property (gobject_class,
241                                      PROP_FORMAT_STRING,
242                                      g_param_spec_string ("format-string",
243                                      "The string to format the clock to",
244                                      "The string to format the clock to",
245                                      NULL,
246                                      G_PARAM_READABLE | G_PARAM_WRITABLE));
247 
248 	g_type_class_add_private (gobject_class, sizeof (GnomeWallClockPrivate));
249 }
250 
251 static void
update_format_string(GnomeWallClock * self,const gchar * format_string)252 update_format_string (GnomeWallClock *self, const gchar *format_string)
253 {
254     guint i;
255     gchar *old_format;
256     gboolean use_24h, show_date, show_seconds;
257     const gchar *default_format, *env_language, *env_lc_time;
258 
259     static const gchar* seconds_tokens[] = {
260         "\%s",
261         "\%S",
262         "\%T",
263         "\%X",
264         "\%c"
265     };
266 
267     ClockInterval new_interval = INTERVAL_MINUTE;
268     gchar *new_format = NULL;
269 
270     /* First parse the settings and fill out our default format strings -
271      * Date-only, Time-only, and combined.
272      */
273 
274     use_24h = g_settings_get_boolean (self->priv->desktop_settings, "clock-use-24h");
275     show_date = g_settings_get_boolean (self->priv->desktop_settings, "clock-show-date");
276     show_seconds = g_settings_get_boolean (self->priv->desktop_settings, "clock-show-seconds");
277 
278     /* Override LANGUAGE with the LC_TIME environment variable
279      * This is needed for gettext to fetch our clock format
280      * according to LC_TIME, and not according to the DE LANGUAGE.
281      */
282     env_language = g_getenv("LANGUAGE");
283     env_lc_time = g_getenv("LC_TIME");
284     if (env_language != NULL && env_lc_time != NULL && env_language != env_lc_time) {
285         g_setenv("LANGUAGE", env_lc_time, TRUE);
286     }
287 
288     if (use_24h) {
289         if (show_date) {
290             /* Translators: This is the time format with full date used
291                in 24-hour mode. */
292             if (show_seconds) {
293                 default_format = WITH_DATE_24H_SECONDS;
294                 self->priv->default_time_format = NO_DATE_24H_SECONDS;
295             } else {
296                 default_format = WITH_DATE_24H;
297                 self->priv->default_time_format = NO_DATE_24H;
298             }
299 
300             self->priv->default_date_format = DATE_ONLY;
301         } else {
302             /* Translators: This is the time format without date used
303                in 24-hour mode. */
304             if (show_seconds) {
305                 default_format = NO_DATE_24H_SECONDS;
306                 self->priv->default_time_format = NO_DATE_24H_SECONDS;
307             } else {
308                 default_format = NO_DATE_24H;
309                 self->priv->default_time_format = NO_DATE_24H;
310             }
311 
312             self->priv->default_date_format = NO_DATE;
313         }
314     } else {
315         if (show_date) {
316             /* Translators: This is a time format with full date used
317                for AM/PM. */
318             if (show_seconds) {
319                 default_format = WITH_DATE_12H_SECONDS;
320                 self->priv->default_time_format = NO_DATE_12H_SECONDS;
321             } else {
322                 default_format = WITH_DATE_12H;
323                 self->priv->default_time_format = NO_DATE_12H;
324             }
325 
326             self->priv->default_date_format = DATE_ONLY;
327         } else {
328             /* Translators: This is a time format without date used
329                for AM/PM. */
330             if (show_seconds) {
331                 default_format = NO_DATE_12H_SECONDS;
332                 self->priv->default_time_format = NO_DATE_12H_SECONDS;
333             } else {
334                 default_format = NO_DATE_12H;
335                 self->priv->default_time_format = NO_DATE_12H;
336             }
337 
338             self->priv->default_date_format = NO_DATE;
339         }
340     }
341 
342     /* Set back LANGUAGE the way it was before */
343     if (env_language != NULL && env_lc_time != NULL && env_language != env_lc_time) {
344         g_setenv("LANGUAGE", env_language, TRUE);
345     }
346 
347     /* Then look at our custom format if we received one, and test it out.
348      * If it's ok, it's used, otherwise we use the default format */
349 
350     if (format_string != NULL) {
351         GDateTime *test_now;
352         gchar *str;
353 
354         test_now = g_date_time_new_now_local ();
355         str = g_date_time_format (test_now, format_string);
356 
357         if (str != NULL) {
358             new_format = g_strdup (format_string);
359         }
360 
361         g_date_time_unref (test_now);
362         g_clear_pointer (&str, g_free);
363     }
364 
365     if (new_format == NULL) {
366         new_format = g_strdup (default_format);
367     }
368 
369     /* Now determine whether we need seconds ticking or not */
370 
371     for (i = 0; i < G_N_ELEMENTS (seconds_tokens); i++) {
372         if (g_strstr_len (new_format, -1, seconds_tokens[i])) {
373             new_interval = INTERVAL_SECOND;
374             break;
375         }
376     }
377 
378     old_format = self->priv->format_string;
379 
380     self->priv->format_string = new_format;
381     self->priv->update_interval = new_interval;
382 
383     g_free (old_format);
384 
385     g_debug ("Updated format string and interval.  '%s', update every %s.",
386              new_format,
387              new_interval == 1 ? "minute" : "second");
388 }
389 
390 static gboolean
update_clock(gpointer data)391 update_clock (gpointer data)
392 {
393 	GnomeWallClock   *self = data;
394 
395 	GSource *source;
396 	GDateTime *now;
397 	GDateTime *expiry;
398 
399 	now = g_date_time_new_now_local ();
400 
401     /* Setup the next update */
402 
403     if (self->priv->update_interval == INTERVAL_SECOND) {
404         expiry = g_date_time_add_seconds (now, 1);
405     } else {
406         expiry = g_date_time_add_seconds (now, 60 - g_date_time_get_second (now));
407     }
408 
409 	if (self->priv->clock_update_id) {
410 		g_source_remove (self->priv->clock_update_id);
411 		self->priv->clock_update_id = 0;
412 	}
413 
414 	source = _gnome_datetime_source_new (now, expiry, TRUE);
415 	g_source_set_priority (source, G_PRIORITY_HIGH);
416 	g_source_set_callback (source, update_clock, self, NULL);
417 	self->priv->clock_update_id = g_source_attach (source, NULL);
418 	g_source_unref (source);
419 
420     /* Update the clock and notify */
421 
422     g_free (self->priv->clock_string);
423 
424     self->priv->clock_string = g_date_time_format (now, self->priv->format_string);
425 
426     g_date_time_unref (now);
427     g_date_time_unref (expiry);
428 
429     g_debug ("Sending clock notify: '%s'", self->priv->clock_string);
430 
431     g_object_notify ((GObject *) self, "clock");
432 
433     return FALSE;
434 }
435 
436 static void
on_schema_change(GSettings * schema,const char * key,gpointer user_data)437 on_schema_change (GSettings *schema,
438                   const char *key,
439                   gpointer user_data)
440 {
441     GnomeWallClock *self = GNOME_WALL_CLOCK (user_data);
442 
443     g_debug ("Updating clock because schema changed");
444 
445     update_format_string (self, self->priv->custom_format ? self->priv->format_string : NULL);
446     update_clock (self);
447 }
448 
449 static void
on_tz_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent * event,gpointer user_data)450 on_tz_changed (GFileMonitor      *monitor,
451                GFile             *file,
452                GFile             *other_file,
453                GFileMonitorEvent *event,
454                gpointer           user_data)
455 {
456     GnomeWallClock *self = GNOME_WALL_CLOCK (user_data);
457 
458     g_debug ("Updating clock because timezone changed");
459 
460     update_format_string (self, self->priv->custom_format ? self->priv->format_string : NULL);
461     update_clock (self);
462 }
463 
464 /**
465  * gnome_wall_clock_get_clock:
466  * @clock: The GnomeWallClock
467  *
468  * Returns a formatted date and time based on either default format
469  * settings, or via a custom-set format string.
470  *
471  * The returned string should be ready to be set on a label.
472  *
473  * Returns: (transfer none): The formatted date/time string.
474  **/
475 
476 const char *
gnome_wall_clock_get_clock(GnomeWallClock * clock)477 gnome_wall_clock_get_clock (GnomeWallClock *clock)
478 {
479 	return clock->priv->clock_string;
480 }
481 
482 /**
483  * gnome_wall_clock_get_default_time_format:
484  * @clock: The GnomeWallClock
485  *
486  * Returns the current time-only format based on current locale
487  * defaults and clock settings.
488  *
489  * Returns: (transfer none): The default time format string.
490  **/
491 const gchar *
gnome_wall_clock_get_default_time_format(GnomeWallClock * clock)492 gnome_wall_clock_get_default_time_format (GnomeWallClock *clock)
493 {
494     return clock->priv->default_time_format;
495 }
496 
497 /**
498  * gnome_wall_clock_get_default_date_format:
499  * @clock: The GnomeWallClock
500  *
501  * Returns the current date-only format based on current locale
502  * defaults and clock settings.
503  *
504  * Returns: (transfer none): The default date format string.
505  **/
506 const gchar *
gnome_wall_clock_get_default_date_format(GnomeWallClock * clock)507 gnome_wall_clock_get_default_date_format (GnomeWallClock *clock)
508 {
509     return clock->priv->default_date_format;
510 }
511 
512 /**
513  * gnome_wall_clock_get_clock_for_format:
514  * @clock: The GnomeWallClock
515  * @format_string: (not nullable)
516  *
517  * Returns a formatted date and time based on the provided format string.
518  * The returned string should be ready to be set on a label.
519  *
520  * Returns: (transfer full): The formatted date/time string, or NULL
521  * if there was a problem with the format string.
522  **/
523 gchar *
gnome_wall_clock_get_clock_for_format(GnomeWallClock * clock,const gchar * format_string)524 gnome_wall_clock_get_clock_for_format (GnomeWallClock *clock,
525                                        const gchar    *format_string)
526 {
527     gchar *ret;
528     GDateTime *now;
529 
530     g_return_val_if_fail (format_string != NULL, NULL);
531 
532     now = g_date_time_new_now_local ();
533     ret = g_date_time_format (now, format_string);
534 
535     g_date_time_unref (now);
536 
537     return ret;
538 }
539 
540 /**
541  * gnome_wall_clock_new:
542  *
543  * Returns a new GnomeWallClock instance
544  *
545  * Returns: A pointer to a new GnomeWallClock instance.
546  **/
547 GnomeWallClock *
gnome_wall_clock_new(void)548 gnome_wall_clock_new (void)
549 {
550     return g_object_new (GNOME_TYPE_WALL_CLOCK, NULL);
551 }
552 
553 /**
554  * gnome_wall_clock_set_format_string:
555  * @clock: The GnomeWallClock
556  * @format_string: (nullable)
557  *
558  * Sets the wall clock to use the provided format string for any
559  * subsequent updates.  Passing NULL will un-set any custom format,
560  * and rely on a default locale format.
561  *
562  * Any invalid format string passed will cause it to be ignored,
563  * and the default locale format used instead.
564  *
565  * Returns: Whether or not the format string was valid and accepted.
566  **/
567 gboolean
gnome_wall_clock_set_format_string(GnomeWallClock * clock,const gchar * format_string)568 gnome_wall_clock_set_format_string (GnomeWallClock *clock,
569                                     const gchar    *format_string)
570 {
571     gboolean ret = FALSE;
572     update_format_string (clock, format_string);
573 
574     if (format_string != NULL) {
575         clock->priv->custom_format = g_strcmp0 (format_string, clock->priv->format_string) == 0;
576         ret = clock->priv->custom_format;
577     } else {
578         clock->priv->custom_format = FALSE;
579         ret = TRUE;
580     }
581 
582     update_clock (clock);
583 
584     return ret;
585 }
586 
587 /**
588  * gnome_wall_clock_lctime_format:
589  * @clock: The GnomeWallClock
590  * @gettext_domain: (nullable)
591  * @format_string: (nullable)
592  *
593  * Returns the translation of the format string according to
594  * the LC_TIME locale.
595  *
596  * Returns: (transfer none): The translated format string.
597  **/
598 gchar *
gnome_wall_clock_lctime_format(const gchar * gettext_domain,const gchar * format_string)599 gnome_wall_clock_lctime_format (const gchar * gettext_domain, const gchar * format_string)
600 {
601   const gchar *env_language, *env_lc_time;
602   gchar *string;
603   gboolean   use_lctime;
604 
605   /* Use LC_TIME if it's set and different than LANGUAGE */
606   env_language = g_getenv("LANGUAGE");
607   env_lc_time = g_getenv("LC_TIME");
608 
609   use_lctime = (env_language != NULL) && (env_lc_time != NULL) && (g_strcmp0 (env_language, env_lc_time) != 0);
610 
611   if (use_lctime) {
612     /* Set LANGUAGE to the LC_TIME value, so we can get the right date format via gettext */
613     g_setenv("LANGUAGE", env_lc_time, TRUE);
614   }
615 
616   string = dgettext(gettext_domain, format_string);
617 
618   if (use_lctime) {
619     /* Set back LANGUAGE the way it was before */
620     g_setenv("LANGUAGE", env_language, TRUE);
621   }
622 
623   return string;
624 }