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 }