/* gcal-context.c * * Copyright 2019 Georges Basile Stavracas Neto * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "GcalContext" #include "config.h" #include "gcal-context.h" #include "gcal-time-zone-monitor.h" #include #include "gdesktop-enum-types.h" #include "gcal-utils.h" #define DESKTOP_SETTINGS_INTERFACE_NAMESPACE "org.gnome.desktop.interface" #define DESKTOP_SETTINGS_CALENDAR_NAMESPACE "org.gnome.desktop.calendar" #define CLOCK_FORMAT_KEY "clock-format" #define CALENDAR_WEEK_START_DAY_KEY "week-start-day" struct _GcalContext { GObject parent; GDBusProxy *settings_portal; GcalClock *clock; GcalManager *manager; GcalSearchEngine *search_engine; GSettings *settings; GcalTimeFormat time_format; GcalWeatherService *weather_service; GcalTimeZoneMonitor *timezone_monitor; GDesktopWeekday week_start_day; }; G_DEFINE_TYPE (GcalContext, gcal_context, G_TYPE_OBJECT) enum { PROP_0, PROP_CLOCK, PROP_MANAGER, PROP_SEARCH_ENGINE, PROP_SETTINGS, PROP_TIME_FORMAT, PROP_TIMEZONE, PROP_WEATHER_SERVICE, PROP_WEEK_START_DAY, N_PROPS }; static GParamSpec *properties [N_PROPS]; /* * Auxiliary methods */ static void set_time_format_from_variant (GcalContext *self, GVariant *variant) { g_autofree gchar *enum_format = NULL; GcalTimeFormat time_format; g_assert (g_variant_type_equal (g_variant_get_type (variant), "s")); if (g_strcmp0 (g_variant_get_string (variant, NULL), "12h") == 0) time_format = GCAL_TIME_FORMAT_12H; else time_format = GCAL_TIME_FORMAT_24H; if (self->time_format == time_format) return; self->time_format = time_format; enum_format = g_enum_to_string (GCAL_TYPE_TIME_FORMAT, self->time_format); g_debug ("Setting time format to %s", enum_format); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIME_FORMAT]); } static void set_week_start_day_from_variant (GcalContext *self, GVariant *variant) { g_autofree gchar *enum_format = NULL; const char *weekday_nick = NULL; GEnumValue *weekday = NULL; g_assert (g_variant_type_equal (g_variant_get_type (variant), "s")); /* extract the day as enum value by name */ weekday_nick = g_variant_get_string (variant, NULL); weekday = g_enum_get_value_by_nick (g_type_class_get (G_DESKTOP_TYPE_WEEKDAY), weekday_nick); g_assert (weekday != NULL); /* find the actual 'default' from locale */ if (weekday->value == G_DESKTOP_WEEKDAY_DEFAULT) weekday = g_enum_get_value (g_type_class_get (G_DESKTOP_TYPE_WEEKDAY), gcal_util_get_first_weekday_iso ()); if (self->week_start_day == weekday->value) return; self->week_start_day = weekday->value; g_debug ("Setting week start day to %s", weekday_nick); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WEEK_START_DAY]); } static gboolean read_variant_from_settings_portal (GcalContext *self, const char *namespace, const char *key, GVariant **value, GError **error) { g_autoptr (GVariant) ret = NULL; g_autoptr (GError) internal_error = NULL; g_assert (value); g_assert (error); ret = g_dbus_proxy_call_sync (self->settings_portal, "ReadOne", g_variant_new ("(ss)", namespace, key), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, NULL, &internal_error); if (internal_error) /* `ret` is also NULL in this case */ { g_propagate_error (error, internal_error); return FALSE; } g_variant_get (ret, "(v)", value); return TRUE; } static gboolean read_time_format (GcalContext *self) { g_autoptr (GVariant) value = NULL; g_autoptr (GError) error = NULL; if (!read_variant_from_settings_portal (self, DESKTOP_SETTINGS_INTERFACE_NAMESPACE, CLOCK_FORMAT_KEY, &value, &error)) { if (error) g_warning ("Failed to read the clock-format setting through the settings portal: %s", error->message); return FALSE; } set_time_format_from_variant (self, value); return TRUE; } static gboolean read_week_start_day (GcalContext *self) { g_autoptr (GVariant) value = NULL; g_autoptr (GError) error = NULL; if (!read_variant_from_settings_portal (self, DESKTOP_SETTINGS_CALENDAR_NAMESPACE, CALENDAR_WEEK_START_DAY_KEY, &value, &error)) { if (error) g_warning ("Failed to read the week-start-day setting through the settings portal: %s", error->message); return FALSE; } set_week_start_day_from_variant (self, value); return TRUE; } /* * Callbacks */ static void on_portal_proxy_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, GcalContext *self) { g_autoptr (GVariant) value = NULL; const char *namespace; const char *name; if (g_strcmp0 (signal_name, "SettingChanged") != 0) return; g_variant_get (parameters, "(&s&sv)", &namespace, &name, &value); if (g_strcmp0 (namespace, DESKTOP_SETTINGS_INTERFACE_NAMESPACE) == 0 && g_strcmp0 (name, CLOCK_FORMAT_KEY) == 0) { set_time_format_from_variant (self, value); } if (g_strcmp0 (namespace, DESKTOP_SETTINGS_CALENDAR_NAMESPACE) == 0 && g_strcmp0 (name, CALENDAR_WEEK_START_DAY_KEY) == 0) { set_week_start_day_from_variant (self, value); } } static void on_timezone_changed_cb (GcalTimeZoneMonitor *timezone_monitor, GParamSpec *pspec, GcalContext *self) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMEZONE]); } /* * GObject overrides */ static void gcal_context_constructed (GObject *object) { GcalContext *self = (GcalContext *)object; G_OBJECT_CLASS (gcal_context_parent_class)->constructed (object); self->manager = gcal_manager_new (); } static void gcal_context_finalize (GObject *object) { GcalContext *self = (GcalContext *)object; gcal_weather_service_stop (self->weather_service); g_clear_object (&self->clock); g_clear_object (&self->settings_portal); g_clear_object (&self->manager); g_clear_object (&self->timezone_monitor); g_clear_object (&self->weather_service); G_OBJECT_CLASS (gcal_context_parent_class)->finalize (object); } static void gcal_context_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GcalContext *self = GCAL_CONTEXT (object); switch (prop_id) { case PROP_CLOCK: g_value_set_object (value, self->clock); break; case PROP_MANAGER: g_value_set_object (value, self->manager); break; case PROP_SEARCH_ENGINE: g_value_set_object (value, self->search_engine); break; case PROP_SETTINGS: g_value_set_object (value, self->settings); break; case PROP_TIME_FORMAT: g_value_set_enum (value, self->time_format); break; case PROP_TIMEZONE: g_value_set_boxed (value, gcal_time_zone_monitor_get_timezone (self->timezone_monitor)); break; case PROP_WEATHER_SERVICE: g_value_set_object (value, self->weather_service); break; case PROP_WEEK_START_DAY: g_value_set_enum (value, self->week_start_day); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gcal_context_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_CLOCK: case PROP_MANAGER: case PROP_SEARCH_ENGINE: case PROP_SETTINGS: case PROP_TIME_FORMAT: case PROP_TIMEZONE: case PROP_WEATHER_SERVICE: case PROP_WEEK_START_DAY: default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gcal_context_class_init (GcalContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = gcal_context_constructed; object_class->finalize = gcal_context_finalize; object_class->get_property = gcal_context_get_property; object_class->set_property = gcal_context_set_property; properties[PROP_CLOCK] = g_param_spec_object ("clock", "Wall clock", "Main clock driving the calendar timeline", GCAL_TYPE_CLOCK, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_MANAGER] = g_param_spec_object ("manager", "Data manager", "Data manager of the application", GCAL_TYPE_MANAGER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_SEARCH_ENGINE] = g_param_spec_object ("search-engine", "Search engine", "Search engine", GCAL_TYPE_SEARCH_ENGINE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_SETTINGS] = g_param_spec_object ("settings", "GNOME Calendar settings", "GNOME Calendar settings", G_TYPE_SETTINGS, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_TIME_FORMAT] = g_param_spec_enum ("time-format", "Time format", "System time format", GCAL_TYPE_TIME_FORMAT, GCAL_TIME_FORMAT_24H, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_TIMEZONE] = g_param_spec_boxed ("timezone", "Timezone", "System timezone", G_TYPE_TIME_ZONE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_WEATHER_SERVICE] = g_param_spec_object ("weather-service", "Weather service", "Weather service", GCAL_TYPE_WEATHER_SERVICE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); properties[PROP_WEEK_START_DAY] = g_param_spec_enum ("week-start-day", NULL, NULL, G_DESKTOP_TYPE_WEEKDAY, gcal_util_get_first_weekday_iso (), G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); } static void gcal_context_init (GcalContext *self) { g_autoptr (GError) error = NULL; self->clock = gcal_clock_new (); self->settings = g_settings_new (SETTINGS_SCHEMA_ID); self->weather_service = gcal_weather_service_new (); self->time_format = GCAL_TIME_FORMAT_24H; self->timezone_monitor = gcal_time_zone_monitor_new (); g_signal_connect_object (self->timezone_monitor, "notify::timezone", G_CALLBACK (on_timezone_changed_cb), self, 0); /* Time format */ self->settings_portal = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", NULL, &error); if (error) g_error ("Failed to load portals: %s. Aborting...", error->message); } /** * gcal_context_new: (skip) * * Create a new GcalContext. Only one context must be * available during the application lifetime. * * Returns: (transfer full): a #GcalContext */ GcalContext* gcal_context_new (void) { return g_object_new (GCAL_TYPE_CONTEXT, NULL); } /** * gcal_context_get_clock: * * Retrieves the #GcalClock from @self. * * Returns: (transfer none): a #GcalClock */ GcalClock* gcal_context_get_clock (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return self->clock; } /** * gcal_context_get_manager: * * Retrieves the #GcalManager from @self. * * Returns: (transfer none): a #GcalManager */ GcalManager* gcal_context_get_manager (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return self->manager; } /** * gcal_context_get_search_engine: * * Retrieves the #GcalSearchEngine from @self. * * Returns: (transfer none): a #GcalSearchEngine */ GcalSearchEngine* gcal_context_get_search_engine (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return self->search_engine; } /** * gcal_context_get_settings: * * Retrieves the #GSettings from @self. * * Returns: (transfer none): a #GSettings */ GSettings* gcal_context_get_settings (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return self->settings; } /** * gcal_context_get_time_format: * * Retrieves the #GcalTimeFormat from @self. * * Returns: a #GcalTimeFormat */ GcalTimeFormat gcal_context_get_time_format (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), 0); return self->time_format; } /** * gcal_context_get_week_start_day: * @self: a #GcalContext instance * * Retrieves the current value of "week-start-day" desktop setting. * Note that this value is synced with the source setting through * the appropriate portal and if you want to be notified of changes * you should register a callback on #GcalContext:week-start-day property. * * When the desktop setting is set to 'G_DESKTOP_WEEKDAY_DEFAULT' * we fall back to 'get_first_weekday_iso()' function from 'gcal-utils.h' * to find the actual day of the week from current locale. * * Returns: the currently set #GDesktopWeekday value */ GDesktopWeekday gcal_context_get_week_start_day (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), gcal_util_get_first_weekday_iso ()); return self->week_start_day; } /** * gcal_context_get_timezone: * * Retrieves the system timezone. * * Returns: (transfer none): a #GTimeZone */ GTimeZone* gcal_context_get_timezone (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return gcal_time_zone_monitor_get_timezone (self->timezone_monitor); } /** * gcal_context_get_weather_service: * * Retrieves the #GcalWeatherService from @self. * * Returns: (transfer none): a #GcalWeatherService */ GcalWeatherService* gcal_context_get_weather_service (GcalContext *self) { g_return_val_if_fail (GCAL_IS_CONTEXT (self), NULL); return self->weather_service; } void gcal_context_startup (GcalContext *self) { gboolean time_format_available; gboolean week_start_day_available; g_return_if_fail (GCAL_IS_CONTEXT (self)); time_format_available = read_time_format (self); week_start_day_available = read_week_start_day (self); /* NOTE: can't inline because of short-circuiting */ if (time_format_available || week_start_day_available) { g_signal_connect (self->settings_portal, "g-signal", G_CALLBACK (on_portal_proxy_signal_cb), self); } gcal_manager_startup (self->manager); g_assert_nonnull (g_application_get_default ()); self->search_engine = gcal_search_engine_new (); }