gnome-shell/src/calendar-server/gnome-shell-calendar-server.c
Gustavo Noronha Silva 404ca91941 calendar-server: Fix double-free detection abort
app_notify_events_added uses an intermediate builder to construct an
array that is then added to the main variant using g_variant_builder_add
which should clear the intermediate, but doesn't due to the way it is
passed: by value, rather than as a pointer.

This was debugged with the help of Eduardo Habkost, who believes it
works on x86 due to big structs being passed as pointers.

Fixed: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3440
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1848>
2021-05-18 23:06:50 +00:00

1095 lines
33 KiB
C

/*
* Copyright (C) 2011 Red Hat, Inc.
*
* 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 2 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 <http://www.gnu.org/licenses/>.
*
* Author: David Zeuthen <davidz@redhat.com>
*
* Based on code from gnome-panel's clock-applet, file calendar-client.c, with Authors:
*
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*
*/
#include "config.h"
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <gio/gio.h>
#define HANDLE_LIBICAL_MEMORY
#define EDS_DISABLE_DEPRECATED
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
#include <libecal/libecal.h>
G_GNUC_END_IGNORE_DEPRECATIONS
#include "calendar-sources.h"
#define BUS_NAME "org.gnome.Shell.CalendarServer"
static const gchar introspection_xml[] =
"<node>"
" <interface name='org.gnome.Shell.CalendarServer'>"
" <method name='SetTimeRange'>"
" <arg type='x' name='since' direction='in'/>"
" <arg type='x' name='until' direction='in'/>"
" <arg type='b' name='force_reload' direction='in'/>"
" </method>"
" <signal name='EventsAddedOrUpdated'>"
" <arg type='a(ssbxxa{sv})' name='events' direction='out'/>"
" </signal>"
" <signal name='EventsRemoved'>"
" <arg type='as' name='ids' direction='out'/>"
" </signal>"
" <signal name='ClientDisappeared'>"
" <arg type='s' name='source_uid' direction='out'/>"
" </signal>"
" <property name='Since' type='x' access='read'/>"
" <property name='Until' type='x' access='read'/>"
" <property name='HasCalendars' type='b' access='read'/>"
" </interface>"
"</node>";
static GDBusNodeInfo *introspection_data = NULL;
struct _App;
typedef struct _App App;
static gboolean opt_replace = FALSE;
static GOptionEntry opt_entries[] = {
{"replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing daemon", NULL},
{NULL }
};
static App *_global_app = NULL;
/* ---------------------------------------------------------------------------------------------------- */
/* While the UID is usually enough to identify an event,
* only the triple of (source,UID,RID) is fully unambiguous;
* neither may contain '\n', so we can safely use it to
* create a unique ID from the triple
*/
static gchar *
create_event_id (const gchar *source_uid,
const gchar *comp_uid,
const gchar *comp_rid)
{
return g_strconcat (
source_uid ? source_uid : "",
"\n",
comp_uid ? comp_uid : "",
"\n",
comp_rid ? comp_rid : "",
NULL);
}
typedef struct
{
ECalClient *client;
GSList **pappointments; /* CalendarAppointment * */
} CollectAppointmentsData;
typedef struct
{
gchar *id;
gchar *summary;
time_t start_time;
time_t end_time;
guint is_all_day : 1;
} CalendarAppointment;
static time_t
get_time_from_property (ECalClient *cal,
ICalComponent *icomp,
ICalPropertyKind prop_kind,
ICalTime * (* get_prop_func) (ICalProperty *prop),
ICalTimezone *default_zone)
{
ICalProperty *prop;
ICalTime *itt;
ICalParameter *param;
ICalTimezone *timezone = NULL;
time_t retval;
prop = i_cal_component_get_first_property (icomp, prop_kind);
if (!prop)
return 0;
itt = get_prop_func (prop);
param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
if (param)
timezone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cal), i_cal_parameter_get_tzid (param));
else if (i_cal_time_is_utc (itt))
timezone = i_cal_timezone_get_utc_timezone ();
else
timezone = default_zone;
i_cal_time_set_timezone (itt, timezone);
retval = i_cal_time_as_timet_with_zone (itt, timezone);
g_clear_object (&param);
g_clear_object (&prop);
g_clear_object (&itt);
return retval;
}
static inline time_t
get_ical_start_time (ECalClient *cal,
ICalComponent *icomp,
ICalTimezone *default_zone)
{
return get_time_from_property (cal,
icomp,
I_CAL_DTSTART_PROPERTY,
i_cal_property_get_dtstart,
default_zone);
}
static inline time_t
get_ical_end_time (ECalClient *cal,
ICalComponent *icomp,
ICalTimezone *default_zone)
{
return get_time_from_property (cal,
icomp,
I_CAL_DTEND_PROPERTY,
i_cal_property_get_dtend,
default_zone);
}
static gboolean
get_ical_is_all_day (ECalClient *cal,
ICalComponent *icomp,
time_t start_time,
ICalTimezone *default_zone)
{
ICalProperty *prop;
ICalDuration *duration;
ICalTime *dtstart;
struct tm *start_tm;
time_t end_time;
gboolean retval;
dtstart = i_cal_component_get_dtstart (icomp);
if (dtstart && i_cal_time_is_date (dtstart))
{
g_clear_object (&dtstart);
return TRUE;
}
g_clear_object (&dtstart);
start_tm = gmtime (&start_time);
if (start_tm->tm_sec != 0 ||
start_tm->tm_min != 0 ||
start_tm->tm_hour != 0)
return FALSE;
if ((end_time = get_ical_end_time (cal, icomp, default_zone)))
return (end_time - start_time) % 86400 == 0;
prop = i_cal_component_get_first_property (icomp, I_CAL_DURATION_PROPERTY);
if (!prop)
return FALSE;
duration = i_cal_property_get_duration (prop);
retval = duration && (i_cal_duration_as_int (duration) % 86400) == 0;
g_clear_object (&duration);
g_clear_object (&prop);
return retval;
}
static CalendarAppointment *
calendar_appointment_new (ECalClient *cal,
ECalComponent *comp)
{
CalendarAppointment *appt;
ICalTimezone *default_zone;
ICalComponent *ical;
ECalComponentId *id;
default_zone = e_cal_client_get_default_timezone (cal);
ical = e_cal_component_get_icalcomponent (comp);
id = e_cal_component_get_id (comp);
appt = g_new0 (CalendarAppointment, 1);
appt->id = create_event_id (e_source_get_uid (e_client_get_source (E_CLIENT (cal))),
id ? e_cal_component_id_get_uid (id) : NULL,
id ? e_cal_component_id_get_rid (id) : NULL);
appt->summary = g_strdup (i_cal_component_get_summary (ical));
appt->start_time = get_ical_start_time (cal, ical, default_zone);
appt->end_time = get_ical_end_time (cal, ical, default_zone);
appt->is_all_day = get_ical_is_all_day (cal,
ical,
appt->start_time,
default_zone);
e_cal_component_id_free (id);
return appt;
}
static void
calendar_appointment_free (gpointer ptr)
{
CalendarAppointment *appt = ptr;
if (appt)
{
g_free (appt->id);
g_free (appt->summary);
g_free (appt);
}
}
static time_t
timet_from_ical_time (ICalTime *time,
ICalTimezone *default_zone)
{
ICalTimezone *timezone = NULL;
timezone = i_cal_time_get_timezone (time);
if (timezone == NULL)
timezone = default_zone;
return i_cal_time_as_timet_with_zone (time, timezone);
}
static gboolean
generate_instances_cb (ICalComponent *icomp,
ICalTime *instance_start,
ICalTime *instance_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
CollectAppointmentsData *data = user_data;
CalendarAppointment *appointment;
ECalComponent *comp;
ICalTimezone *default_zone;
default_zone = e_cal_client_get_default_timezone (data->client);
comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
appointment = calendar_appointment_new (data->client, comp);
appointment->start_time = timet_from_ical_time (instance_start, default_zone);
appointment->end_time = timet_from_ical_time (instance_end, default_zone);
*(data->pappointments) = g_slist_prepend (*(data->pappointments), appointment);
g_clear_object (&comp);
return TRUE;
}
/* ---------------------------------------------------------------------------------------------------- */
struct _App
{
GDBusConnection *connection;
time_t since;
time_t until;
ICalTimezone *zone;
CalendarSources *sources;
gulong client_appeared_signal_id;
gulong client_disappeared_signal_id;
gchar *timezone_location;
GSList *notify_appointments; /* CalendarAppointment *, for EventsAdded */
GSList *notify_ids; /* gchar *, for EventsRemoved */
GSList *live_views;
};
static void
app_update_timezone (App *app)
{
g_autofree char *location = NULL;
location = e_cal_system_timezone_get_location ();
if (g_strcmp0 (location, app->timezone_location) != 0)
{
if (location == NULL)
app->zone = i_cal_timezone_get_utc_timezone ();
else
app->zone = i_cal_timezone_get_builtin_timezone (location);
g_free (app->timezone_location);
app->timezone_location = g_steal_pointer (&location);
print_debug ("Using timezone %s", app->timezone_location);
}
}
static void
app_notify_events_added (App *app)
{
GVariantBuilder builder, extras_builder;
GSList *events, *link;
events = g_slist_reverse (app->notify_appointments);
app->notify_appointments = NULL;
print_debug ("Emitting EventsAddedOrUpdated with %d events", g_slist_length (events));
if (!events)
return;
/* The a{sv} is used as an escape hatch in case we want to provide more
* information in the future without breaking ABI
*/
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssbxxa{sv})"));
for (link = events; link; link = g_slist_next (link))
{
CalendarAppointment *appt = link->data;
time_t start_time = appt->start_time;
time_t end_time = appt->end_time;
if ((start_time >= app->since &&
start_time < app->until) ||
(start_time <= app->since &&
(end_time - 1) > app->since))
{
g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder,
"(ssbxxa{sv})",
appt->id,
appt->summary != NULL ? appt->summary : "",
(gboolean) appt->is_all_day,
(gint64) start_time,
(gint64) end_time,
&extras_builder);
}
}
g_dbus_connection_emit_signal (app->connection,
NULL, /* destination_bus_name */
"/org/gnome/Shell/CalendarServer",
"org.gnome.Shell.CalendarServer",
"EventsAddedOrUpdated",
g_variant_new ("(a(ssbxxa{sv}))", &builder),
NULL);
g_variant_builder_clear (&builder);
g_slist_free_full (events, calendar_appointment_free);
}
static void
app_notify_events_removed (App *app)
{
GVariantBuilder builder;
GSList *ids, *link;
ids = app->notify_ids;
app->notify_ids = NULL;
print_debug ("Emitting EventsRemoved with %d ids", g_slist_length (ids));
if (!ids)
return;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
for (link = ids; link; link = g_slist_next (link))
{
const gchar *id = link->data;
g_variant_builder_add (&builder, "s", id);
}
g_dbus_connection_emit_signal (app->connection,
NULL, /* destination_bus_name */
"/org/gnome/Shell/CalendarServer",
"org.gnome.Shell.CalendarServer",
"EventsRemoved",
g_variant_new ("(as)", &builder),
NULL);
g_variant_builder_clear (&builder);
g_slist_free_full (ids, g_free);
return;
}
static void
app_process_added_modified_objects (App *app,
ECalClientView *view,
GSList *objects) /* ICalComponent * */
{
ECalClient *cal_client;
GSList *link;
gboolean expand_recurrences;
cal_client = e_cal_client_view_ref_client (view);
expand_recurrences = e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
for (link = objects; link; link = g_slist_next (link))
{
ECalComponent *comp;
ICalComponent *icomp = link->data;
if (!icomp || !i_cal_component_get_uid (icomp))
continue;
if (expand_recurrences &&
!e_cal_util_component_is_instance (icomp) &&
e_cal_util_component_has_recurrences (icomp))
{
CollectAppointmentsData data;
data.client = cal_client;
data.pappointments = &app->notify_appointments;
e_cal_client_generate_instances_for_object_sync (cal_client, icomp, app->since, app->until, NULL,
generate_instances_cb, &data);
}
else
{
comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
if (!comp)
continue;
app->notify_appointments = g_slist_prepend (app->notify_appointments,
calendar_appointment_new (cal_client, comp));
g_object_unref (comp);
}
}
g_clear_object (&cal_client);
if (app->notify_appointments)
app_notify_events_added (app);
}
static void
on_objects_added (ECalClientView *view,
GSList *objects,
gpointer user_data)
{
App *app = user_data;
ECalClient *client;
client = e_cal_client_view_ref_client (view);
print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
g_clear_object (&client);
app_process_added_modified_objects (app, view, objects);
}
static void
on_objects_modified (ECalClientView *view,
GSList *objects,
gpointer user_data)
{
App *app = user_data;
ECalClient *client;
client = e_cal_client_view_ref_client (view);
print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
g_clear_object (&client);
app_process_added_modified_objects (app, view, objects);
}
static void
on_objects_removed (ECalClientView *view,
GSList *uids,
gpointer user_data)
{
App *app = user_data;
ECalClient *client;
GSList *link;
const gchar *source_uid;
client = e_cal_client_view_ref_client (view);
source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (uids), source_uid);
for (link = uids; link; link = g_slist_next (link))
{
ECalComponentId *id = link->data;
if (!id)
continue;
app->notify_ids = g_slist_prepend (app->notify_ids,
create_event_id (source_uid,
e_cal_component_id_get_uid (id),
e_cal_component_id_get_rid (id)));
}
g_clear_object (&client);
if (app->notify_ids)
app_notify_events_removed (app);
}
static gboolean
app_has_calendars (App *app)
{
return app->live_views != NULL;
}
static ECalClientView *
app_start_view (App *app,
ECalClient *cal_client)
{
g_autofree char *since_iso8601 = NULL;
g_autofree char *until_iso8601 = NULL;
g_autofree char *query = NULL;
const gchar *tz_location;
ECalClientView *view = NULL;
g_autoptr (GError) error = NULL;
if (app->since <= 0 || app->since >= app->until)
return NULL;
if (!app->since || !app->until)
{
print_debug ("Skipping load of events, no time interval set yet");
return NULL;
}
/* timezone could have changed */
app_update_timezone (app);
since_iso8601 = isodate_from_time_t (app->since);
until_iso8601 = isodate_from_time_t (app->until);
tz_location = i_cal_timezone_get_location (app->zone);
print_debug ("Loading events since %s until %s for calendar '%s'",
since_iso8601,
until_iso8601,
e_source_get_uid (e_client_get_source (E_CLIENT (cal_client))));
query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
"(make-time \"%s\") \"%s\"",
since_iso8601,
until_iso8601,
tz_location);
e_cal_client_set_default_timezone (cal_client, app->zone);
if (!e_cal_client_get_view_sync (cal_client, query, &view, NULL /* cancellable */, &error))
{
g_warning ("Error setting up live-query '%s' on calendar: %s\n", query, error ? error->message : "Unknown error");
view = NULL;
}
else
{
g_signal_connect (view,
"objects-added",
G_CALLBACK (on_objects_added),
app);
g_signal_connect (view,
"objects-modified",
G_CALLBACK (on_objects_modified),
app);
g_signal_connect (view,
"objects-removed",
G_CALLBACK (on_objects_removed),
app);
e_cal_client_view_start (view, NULL);
}
return view;
}
static void
app_stop_view (App *app,
ECalClientView *view)
{
e_cal_client_view_stop (view, NULL);
g_signal_handlers_disconnect_by_func (view, on_objects_added, app);
g_signal_handlers_disconnect_by_func (view, on_objects_modified, app);
g_signal_handlers_disconnect_by_func (view, on_objects_removed, app);
}
static void
app_notify_has_calendars (App *app)
{
GVariantBuilder dict_builder;
g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&dict_builder, "{sv}", "HasCalendars",
g_variant_new_boolean (app_has_calendars (app)));
g_dbus_connection_emit_signal (app->connection,
NULL,
"/org/gnome/Shell/CalendarServer",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
g_variant_new ("(sa{sv}as)",
"org.gnome.Shell.CalendarServer",
&dict_builder,
NULL),
NULL);
g_variant_builder_clear (&dict_builder);
}
static void
app_update_views (App *app)
{
GSList *link, *clients;
gboolean had_views, has_views;
had_views = app->live_views != NULL;
for (link = app->live_views; link; link = g_slist_next (link))
{
app_stop_view (app, link->data);
}
g_slist_free_full (app->live_views, g_object_unref);
app->live_views = NULL;
clients = calendar_sources_ref_clients (app->sources);
for (link = clients; link; link = g_slist_next (link))
{
ECalClient *cal_client = link->data;
ECalClientView *view;
if (!cal_client)
continue;
view = app_start_view (app, cal_client);
if (view)
app->live_views = g_slist_prepend (app->live_views, view);
}
has_views = app->live_views != NULL;
if (has_views != had_views)
app_notify_has_calendars (app);
g_slist_free_full (clients, g_object_unref);
}
static void
on_client_appeared_cb (CalendarSources *sources,
ECalClient *client,
gpointer user_data)
{
App *app = user_data;
ECalClientView *view;
GSList *link;
const gchar *source_uid;
source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
print_debug ("Client appeared '%s'", source_uid);
for (link = app->live_views; link; link = g_slist_next (link))
{
ECalClientView *view = link->data;
ECalClient *cal_client;
ESource *source;
cal_client = e_cal_client_view_ref_client (view);
source = e_client_get_source (E_CLIENT (cal_client));
if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
{
g_clear_object (&cal_client);
return;
}
g_clear_object (&cal_client);
}
view = app_start_view (app, client);
if (view)
{
app->live_views = g_slist_prepend (app->live_views, view);
/* It's the first view, notify that it has calendars now */
if (!g_slist_next (app->live_views))
app_notify_has_calendars (app);
}
}
static void
on_client_disappeared_cb (CalendarSources *sources,
const gchar *source_uid,
gpointer user_data)
{
App *app = user_data;
GSList *link;
print_debug ("Client disappeared '%s'", source_uid);
for (link = app->live_views; link; link = g_slist_next (link))
{
ECalClientView *view = link->data;
ECalClient *cal_client;
ESource *source;
cal_client = e_cal_client_view_ref_client (view);
source = e_client_get_source (E_CLIENT (cal_client));
if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
{
g_clear_object (&cal_client);
app_stop_view (app, view);
app->live_views = g_slist_remove (app->live_views, view);
g_object_unref (view);
print_debug ("Emitting ClientDisappeared for '%s'", source_uid);
g_dbus_connection_emit_signal (app->connection,
NULL, /* destination_bus_name */
"/org/gnome/Shell/CalendarServer",
"org.gnome.Shell.CalendarServer",
"ClientDisappeared",
g_variant_new ("(s)", source_uid),
NULL);
/* It was the last view, notify that it doesn't have calendars now */
if (!app->live_views)
app_notify_has_calendars (app);
break;
}
g_clear_object (&cal_client);
}
}
static App *
app_new (GDBusConnection *connection)
{
App *app;
app = g_new0 (App, 1);
app->connection = g_object_ref (connection);
app->sources = calendar_sources_get ();
app->client_appeared_signal_id = g_signal_connect (app->sources,
"client-appeared",
G_CALLBACK (on_client_appeared_cb),
app);
app->client_disappeared_signal_id = g_signal_connect (app->sources,
"client-disappeared",
G_CALLBACK (on_client_disappeared_cb),
app);
app_update_timezone (app);
return app;
}
static void
app_free (App *app)
{
GSList *ll;
for (ll = app->live_views; ll != NULL; ll = g_slist_next (ll))
{
ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
app_stop_view (app, view);
}
g_signal_handler_disconnect (app->sources,
app->client_appeared_signal_id);
g_signal_handler_disconnect (app->sources,
app->client_disappeared_signal_id);
g_free (app->timezone_location);
g_slist_free_full (app->live_views, g_object_unref);
g_slist_free_full (app->notify_appointments, calendar_appointment_free);
g_slist_free_full (app->notify_ids, g_free);
g_object_unref (app->connection);
g_object_unref (app->sources);
g_free (app);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
handle_method_call (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
App *app = user_data;
if (g_strcmp0 (method_name, "SetTimeRange") == 0)
{
gint64 since;
gint64 until;
gboolean force_reload = FALSE;
gboolean window_changed = FALSE;
g_variant_get (parameters,
"(xxb)",
&since,
&until,
&force_reload);
if (until < since)
{
g_dbus_method_invocation_return_dbus_error (invocation,
"org.gnome.Shell.CalendarServer.Error.Failed",
"until cannot be before since");
goto out;
}
print_debug ("Handling SetTimeRange (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
since,
until,
force_reload ? "true" : "false");
if (app->until != until || app->since != since)
{
GVariantBuilder *builder;
GVariantBuilder *invalidated_builder;
app->until = until;
app->since = since;
window_changed = TRUE;
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
g_variant_builder_add (builder, "{sv}",
"Until", g_variant_new_int64 (app->until));
g_variant_builder_add (builder, "{sv}",
"Since", g_variant_new_int64 (app->since));
g_dbus_connection_emit_signal (app->connection,
NULL, /* destination_bus_name */
"/org/gnome/Shell/CalendarServer",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
g_variant_new ("(sa{sv}as)",
"org.gnome.Shell.CalendarServer",
builder,
invalidated_builder),
NULL); /* GError** */
g_variant_builder_unref (builder);
g_variant_builder_unref (invalidated_builder);
}
g_dbus_method_invocation_return_value (invocation, NULL);
if (window_changed || force_reload)
app_update_views (app);
}
else
{
g_assert_not_reached ();
}
out:
;
}
static GVariant *
handle_get_property (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data)
{
App *app = user_data;
GVariant *ret;
ret = NULL;
if (g_strcmp0 (property_name, "Since") == 0)
{
ret = g_variant_new_int64 (app->since);
}
else if (g_strcmp0 (property_name, "Until") == 0)
{
ret = g_variant_new_int64 (app->until);
}
else if (g_strcmp0 (property_name, "HasCalendars") == 0)
{
ret = g_variant_new_boolean (app_has_calendars (app));
}
else
{
g_assert_not_reached ();
}
return ret;
}
static const GDBusInterfaceVTable interface_vtable =
{
handle_method_call,
handle_get_property,
NULL /* handle_set_property */
};
static void
on_bus_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
GMainLoop *main_loop = user_data;
guint registration_id;
g_autoptr (GError) error = NULL;
_global_app = app_new (connection);
registration_id = g_dbus_connection_register_object (connection,
"/org/gnome/Shell/CalendarServer",
introspection_data->interfaces[0],
&interface_vtable,
_global_app,
NULL, /* user_data_free_func */
&error);
if (registration_id == 0)
{
g_printerr ("Error exporting object: %s (%s %d)\n",
error->message,
g_quark_to_string (error->domain),
error->code);
g_main_loop_quit (main_loop);
return;
}
print_debug ("Connected to the session bus");
}
static void
on_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
GMainLoop *main_loop = user_data;
g_print ("gnome-shell-calendar-server[%d]: Lost (or failed to acquire) the name " BUS_NAME " - exiting\n",
(gint) getpid ());
g_main_loop_quit (main_loop);
}
static void
on_name_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
print_debug ("Acquired the name " BUS_NAME);
}
static gboolean
stdin_channel_io_func (GIOChannel *source,
GIOCondition condition,
gpointer data)
{
GMainLoop *main_loop = data;
if (condition & G_IO_HUP)
{
g_debug ("gnome-shell-calendar-server[%d]: Got HUP on stdin - exiting\n",
(gint) getpid ());
g_main_loop_quit (main_loop);
}
else
{
g_warning ("Unhandled condition %d on GIOChannel for stdin", condition);
}
return FALSE; /* remove source */
}
int
main (int argc,
char **argv)
{
g_autoptr (GError) error = NULL;
GOptionContext *opt_context;
GMainLoop *main_loop;
gint ret;
guint name_owner_id;
GIOChannel *stdin_channel;
ret = 1;
opt_context = NULL;
name_owner_id = 0;
stdin_channel = NULL;
introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
g_assert (introspection_data != NULL);
opt_context = g_option_context_new ("gnome-shell calendar server");
g_option_context_add_main_entries (opt_context, opt_entries, NULL);
if (!g_option_context_parse (opt_context, &argc, &argv, &error))
{
g_printerr ("Error parsing options: %s\n", error->message);
goto out;
}
main_loop = g_main_loop_new (NULL, FALSE);
stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
g_io_add_watch_full (stdin_channel,
G_PRIORITY_DEFAULT,
G_IO_HUP,
stdin_channel_io_func,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
BUS_NAME,
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
(opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
on_bus_acquired,
on_name_acquired,
on_name_lost,
g_main_loop_ref (main_loop),
(GDestroyNotify) g_main_loop_unref);
g_main_loop_run (main_loop);
g_main_loop_unref (main_loop);
ret = 0;
out:
if (stdin_channel != NULL)
g_io_channel_unref (stdin_channel);
if (_global_app != NULL)
app_free (_global_app);
if (name_owner_id != 0)
g_bus_unown_name (name_owner_id);
if (opt_context != NULL)
g_option_context_free (opt_context);
return ret;
}