diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/main.c | 96 | ||||
-rw-r--r-- | src/service.c | 638 | ||||
-rw-r--r-- | src/service.h | 71 |
4 files changed, 810 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 9350f86..fee2245 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,9 @@ indicator_datetime_service_SOURCES = \ planner.h \ planner-eds.c \ planner-eds.h \ - datetime-service.c \ + service.c \ + service.h \ + main.c \ timezone.c \ timezone.h \ timezone-file.c \ @@ -26,7 +28,8 @@ indicator_datetime_service_SOURCES = \ dbus-shared.h \ settings-shared.h indicator_datetime_service_CFLAGS = \ - -Wall -Wextra \ + -Wall \ + -Wextra -Wno-missing-field-initializers \ -Werror \ $(SERVICE_CFLAGS) \ $(COVERAGE_CFLAGS) \ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..162bb16 --- /dev/null +++ b/src/main.c @@ -0,0 +1,96 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <locale.h> +#include <stdlib.h> /* exit() */ + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "service.h" + +/*** +**** +***/ + +static gboolean replace = FALSE; + +static void +parse_command_line (int * argc, char *** argv) +{ + GError * error; + GOptionContext * option_context; + + static GOptionEntry entries[] = + { + { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace the currently-running service", NULL }, + { NULL } + }; + + error = NULL; + option_context = g_option_context_new ("- indicator-datetime service"); + g_option_context_add_main_entries (option_context, entries, GETTEXT_PACKAGE); + if (!g_option_context_parse (option_context, argc, argv, &error)) + { + g_print ("option parsing failed: %s\n", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + g_option_context_free (option_context); +} + +/*** +**** +***/ + +static void +on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) +{ + g_debug ("exiting: service couldn't acquire or lost ownership of busname"); + g_main_loop_quit ((GMainLoop*)loop); +} + +int +main (int argc, char ** argv) +{ + GMainLoop * loop; + IndicatorDatetimeService * service; + + /* boilerplate i18n */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + + parse_command_line (&argc, &argv); + + /* run */ + service = indicator_datetime_service_new (replace); + loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (service, INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, + G_CALLBACK(on_name_lost), loop); + g_main_loop_run (loop); + + /* cleanup */ + g_clear_object (&service); + g_main_loop_unref (loop); + return 0; +} diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..f5e16a0 --- /dev/null +++ b/src/service.c @@ -0,0 +1,638 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>. + */ + +#include <locale.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "service.h" + +/* FIXME: remove -test */ +#define BUS_NAME "com.canonical.indicator.datetime-test" +#define BUS_PATH "/com/canonical/indicator/datetime" + +G_DEFINE_TYPE (IndicatorDatetimeService, + indicator_datetime_service, + G_TYPE_OBJECT) + +/* signals enum */ +enum +{ + NAME_LOST, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +enum +{ + PROP_0, + PROP_REPLACE, + PROP_LAST +}; + +static GParamSpec * properties[PROP_LAST]; + +enum +{ + SECTION_HEADER = (1<<0), + SECTION_CALENDAR = (1<<1), + SECTION_APPOINTMENTS = (1<<2), + SECTION_LOCATIONS = (1<<3), + SECTION_SETTINGS = (1<<4), +}; + +enum +{ + PROFILE_DESKTOP, + PROFILE_GREETER, + N_PROFILES +}; + +static const char * const menu_names[N_PROFILES] = +{ + "desktop", + "desktop_greeter" +}; + +struct ProfileMenuInfo +{ + /* the root level -- the header is the only child of this */ + GMenu * menu; + + /* parent of the sections. This is the header's submenu */ + GMenu * submenu; + + guint export_id; +}; + +struct _IndicatorDatetimeServicePrivate +{ + guint own_id; + GSimpleActionGroup * actions; + guint actions_export_id; + struct ProfileMenuInfo menus[N_PROFILES]; + guint rebuild_id; + int rebuild_flags; + GDBusConnection * conn; + GCancellable * cancellable; + GSimpleAction * header_action; + + gboolean replace; +}; + +typedef IndicatorDatetimeServicePrivate priv_t; + +/*** +**** +***/ + +static void rebuild_now (IndicatorDatetimeService * self, int section); +static void rebuild_soon (IndicatorDatetimeService * self, int section); + +static inline void +rebuild_header_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_HEADER); +} +static inline void +rebuild_calendar_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_CALENDAR); +} +static inline void +rebuild_appointments_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_APPOINTMENTS); +} +static inline void +rebuild_locations_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_LOCATIONS); +} +static inline void +rebuild_settings_section_soon (IndicatorDatetimeService * self) +{ + rebuild_soon (self, SECTION_SETTINGS); +} + +/*** +**** +***/ + +static void +update_header_action (IndicatorDatetimeService * self) +{ + GVariant * v; + gchar * a11y = g_strdup ("a11y"); + const gchar * label = "Hello World"; + const gchar * iconstr = "icon"; + const priv_t * const p = self->priv; + + g_return_if_fail (p->header_action != NULL); + + v = g_variant_new ("(sssb)", label, iconstr, a11y, TRUE); + g_simple_action_set_state (p->header_action, v); + g_free (a11y); +} + +/*** +**** +***/ + +static GMenuModel * +create_calendar_section (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + GMenu * menu; + + menu = g_menu_new (); + + return G_MENU_MODEL (menu); +} + +static GMenuModel * +create_appointments_section (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + GMenu * menu; + + menu = g_menu_new (); + + return G_MENU_MODEL (menu); +} + +static GMenuModel * +create_locations_section (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + GMenu * menu; + + menu = g_menu_new (); + + return G_MENU_MODEL (menu); +} + +static GMenuModel * +create_settings_section (IndicatorDatetimeService * self G_GNUC_UNUSED) +{ + GMenu * menu; + + menu = g_menu_new (); + + g_menu_append (menu, _("Date and Time Settings\342\200\246"), "indicator.activateSettings"); + + return G_MENU_MODEL (menu); +} + +static void +create_menu (IndicatorDatetimeService * self, int profile) +{ + GMenu * menu; + GMenu * submenu; + GMenuItem * header; + GMenuModel * sections[16]; + int i; + int n = 0; + + g_assert (0<=profile && profile<N_PROFILES); + g_assert (self->priv->menus[profile].menu == NULL); + + if (profile == PROFILE_DESKTOP) + { + sections[n++] = create_calendar_section (self); + sections[n++] = create_appointments_section (self); + sections[n++] = create_locations_section (self); + sections[n++] = create_settings_section (self); + } + else if (profile == PROFILE_GREETER) + { + /* FIXME: what goes here? */ + } + + /* add sections to the submenu */ + submenu = g_menu_new (); + for (i=0; i<n; ++i) + { + g_menu_append_section (submenu, NULL, sections[i]); + g_object_unref (sections[i]); + } + + /* add submenu to the header */ + header = g_menu_item_new (NULL, "indicator._header"); + g_menu_item_set_attribute (header, "x-canonical-type", "s", "com.canonical.indicator.root"); + g_menu_item_set_submenu (header, G_MENU_MODEL (submenu)); + g_object_unref (submenu); + + /* add header to the menu */ + menu = g_menu_new (); + g_menu_append_item (menu, header); + g_object_unref (header); + + self->priv->menus[profile].menu = menu; + self->priv->menus[profile].submenu = submenu; +} + +/*** +**** GActions +***/ + +static void +on_settings_activated (GSimpleAction * a G_GNUC_UNUSED, + GVariant * param G_GNUC_UNUSED, + gpointer gself G_GNUC_UNUSED) +{ + g_message ("settings activated"); +} + +static void +init_gactions (IndicatorDatetimeService * self) +{ + GVariant * v; + GSimpleAction * a; + priv_t * p = self->priv; + + GActionEntry entries[] = { + { "activateSettings", on_settings_activated, NULL, NULL, NULL }, + }; + + p->actions = g_simple_action_group_new (); + + g_action_map_add_action_entries (G_ACTION_MAP(p->actions), + entries, + G_N_ELEMENTS(entries), + self); + + /* add the header action */ + v = g_variant_new ("(sssb)", "Hello World", "icon", "a11y", TRUE); + a = g_simple_action_new_stateful ("_header", NULL, v); + g_simple_action_group_insert (p->actions, G_ACTION(a)); + p->header_action = a; + + rebuild_now (self, SECTION_HEADER); +} + +/*** +**** +***/ + +/** + * A small helper function for rebuild_now(). + * - removes the previous section + * - adds and unrefs the new section + */ +static void +rebuild_section (GMenu * parent, int pos, GMenuModel * new_section) +{ + g_menu_remove (parent, pos); + g_menu_insert_section (parent, pos, NULL, new_section); + g_object_unref (new_section); +} + +static void +rebuild_now (IndicatorDatetimeService * self, int sections) +{ + priv_t * p = self->priv; + struct ProfileMenuInfo * desktop = &p->menus[PROFILE_DESKTOP]; + //struct ProfileMenuInfo * greeter = &p->menus[PROFILE_GREETER]; + + if (sections & SECTION_HEADER) + { + update_header_action (self); + } + + if (sections & SECTION_CALENDAR) + { + rebuild_section (desktop->submenu, 0, create_calendar_section (self)); + } + + if (sections & SECTION_APPOINTMENTS) + { + rebuild_section (desktop->submenu, 1, create_appointments_section (self)); + } + + if (sections & SECTION_LOCATIONS) + { + rebuild_section (desktop->submenu, 2, create_locations_section (self)); + } + + if (sections & SECTION_SETTINGS) + { + rebuild_section (desktop->submenu, 3, create_settings_section (self)); + //rebuild_section (greeter->submenu, 0, create_datetime_section(self)); + } +} + +static int +rebuild_timeout_func (IndicatorDatetimeService * self) +{ + priv_t * p = self->priv; + rebuild_now (self, p->rebuild_flags); + p->rebuild_flags = 0; + p->rebuild_id = 0; + return G_SOURCE_REMOVE; +} + +static void +rebuild_soon (IndicatorDatetimeService * self, int section) +{ + priv_t * p = self->priv; + + p->rebuild_flags |= section; + + if (p->rebuild_id == 0) + { + /* Change events seem to come over the bus in small bursts. This msec + value is an arbitrary number that tries to be large enough to fold + multiple events into a single rebuild, but small enough that the + user won't notice any lag. */ + static const int REBUILD_INTERVAL_MSEC = 500; + + p->rebuild_id = g_timeout_add (REBUILD_INTERVAL_MSEC, + (GSourceFunc)rebuild_timeout_func, + self); + } +} + +/*** +**** GDBus +***/ + +static void +on_bus_acquired (GDBusConnection * connection, + const gchar * name, + gpointer gself) +{ + int i; + guint id; + GError * err = NULL; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(gself); + priv_t * p = self->priv; + + g_debug ("bus acquired: %s", name); + + p->conn = g_object_ref (G_OBJECT (connection)); + + /* export the actions */ + if ((id = g_dbus_connection_export_action_group (connection, + BUS_PATH, + G_ACTION_GROUP (p->actions), + &err))) + { + p->actions_export_id = id; + } + else + { + g_warning ("cannot export action group: %s", err->message); + g_clear_error (&err); + } + + /* export the menus */ + for (i=0; i<N_PROFILES; ++i) + { + char * path = g_strdup_printf ("%s/%s", BUS_PATH, menu_names[i]); + struct ProfileMenuInfo * menu = &p->menus[i]; + + if (menu->menu == NULL) + create_menu (self, i); + + if ((id = g_dbus_connection_export_menu_model (connection, + path, + G_MENU_MODEL (menu->menu), + &err))) + { + menu->export_id = id; + } + else + { + g_warning ("cannot export %s menu: %s", menu_names[i], err->message); + g_clear_error (&err); + } + + g_free (path); + } +} + +static void +unexport (IndicatorDatetimeService * self) +{ + int i; + priv_t * p = self->priv; + + /* unexport the menus */ + for (i=0; i<N_PROFILES; ++i) + { + guint * id = &self->priv->menus[i].export_id; + + if (*id) + { + g_dbus_connection_unexport_menu_model (p->conn, *id); + *id = 0; + } + } + + /* unexport the actions */ + if (p->actions_export_id) + { + g_dbus_connection_unexport_action_group (p->conn, p->actions_export_id); + p->actions_export_id = 0; + } +} + +static void +on_name_lost (GDBusConnection * connection G_GNUC_UNUSED, + const gchar * name, + gpointer gself) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself); + + g_debug ("%s %s name lost %s", G_STRLOC, G_STRFUNC, name); + + unexport (self); + + g_signal_emit (self, signals[NAME_LOST], 0, NULL); +} + +/*** +**** GObject virtual functions +***/ + +static void +my_constructed (GObject * o) +{ + GBusNameOwnerFlags owner_flags; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); + + /* own the name in constructed() instead of init() so that + we'll know the value of the 'replace' property */ + owner_flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + if (self->priv->replace) + owner_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + + self->priv->own_id = g_bus_own_name (G_BUS_TYPE_SESSION, + BUS_NAME, + owner_flags, + on_bus_acquired, + NULL, + on_name_lost, + self, + NULL); +} + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); + + switch (property_id) + { + case PROP_REPLACE: + g_value_set_boolean (value, self->priv->replace); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o); + + switch (property_id) + { + case PROP_REPLACE: + self->priv->replace = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + } +} + +static void +my_dispose (GObject * o) +{ + int i; + IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE(o); + priv_t * p = self->priv; + + if (p->own_id) + { + g_bus_unown_name (p->own_id); + p->own_id = 0; + } + + unexport (self); + + if (p->cancellable != NULL) + { + g_cancellable_cancel (p->cancellable); + g_clear_object (&p->cancellable); + } + + if (p->rebuild_id) + { + g_source_remove (p->rebuild_id); + p->rebuild_id = 0; + } + + g_clear_object (&p->actions); + + for (i=0; i<N_PROFILES; ++i) + g_clear_object (&p->menus[i].menu); + + g_clear_object (&p->header_action); + g_clear_object (&p->conn); + + G_OBJECT_CLASS (indicator_datetime_service_parent_class)->dispose (o); +} + +/*** +**** Instantiation +***/ + +static void +indicator_datetime_service_init (IndicatorDatetimeService * self) +{ + priv_t * p; + + /* init our priv pointer */ + p = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_TYPE_DATETIME_SERVICE, + IndicatorDatetimeServicePrivate); + self->priv = p; + + /* init the backend objects */ + p->cancellable = g_cancellable_new (); + + init_gactions (self); +} + +static void +indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_dispose; + object_class->constructed = my_constructed; + object_class->get_property = my_get_property; + object_class->set_property = my_set_property; + + g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate)); + + signals[NAME_LOST] = g_signal_new (INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IndicatorDatetimeServiceClass, name_lost), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + properties[PROP_0] = NULL; + + properties[PROP_REPLACE] = g_param_spec_boolean ("replace", + "Replace Service", + "Replace existing service", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST, properties); +} + +/*** +**** Public API +***/ + +IndicatorDatetimeService * +indicator_datetime_service_new (gboolean replace) +{ + GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, + "replace", replace, + NULL); + + return INDICATOR_DATETIME_SERVICE (o); +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..594d7fe --- /dev/null +++ b/src/service.h @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __INDICATOR_DATETIME_SERVICE_H__ +#define __INDICATOR_DATETIME_SERVICE_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +/* standard GObject macros */ +#define INDICATOR_TYPE_DATETIME_SERVICE (indicator_datetime_service_get_type()) +#define INDICATOR_DATETIME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_SERVICE, IndicatorDatetimeService)) +#define INDICATOR_DATETIME_SERVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_SERVICE, IndicatorDatetimeServiceClass)) +#define INDICATOR_DATETIME_SERVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), INDICATOR_TYPE_DATETIME_SERVICE, IndicatorDatetimeServiceClass)) +#define INDICATOR_IS_DATETIME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_SERVICE)) + +typedef struct _IndicatorDatetimeService IndicatorDatetimeService; +typedef struct _IndicatorDatetimeServiceClass IndicatorDatetimeServiceClass; +typedef struct _IndicatorDatetimeServicePrivate IndicatorDatetimeServicePrivate; + +/* signal keys */ +#define INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST "name-lost" + +/** + * The Indicator Datetime Service. + */ +struct _IndicatorDatetimeService +{ + /*< private >*/ + GObject parent; + IndicatorDatetimeServicePrivate * priv; +}; + +struct _IndicatorDatetimeServiceClass +{ + GObjectClass parent_class; + + /* signals */ + + void (* name_lost)(IndicatorDatetimeService * self); +}; + +/*** +**** +***/ + +GType indicator_datetime_service_get_type (void); + +IndicatorDatetimeService * indicator_datetime_service_new (gboolean replace); + +G_END_DECLS + +#endif /* __INDICATOR_DATETIME_SERVICE_H__ */ |