Move calendar handling out-of-process

Unfortunately the evolution-data-server client-side libraries seem to
block the calling thread. This is a major problem as we must never
ever block the main thread (doing so causes animations to flicker
etc.). In the worst case, this problem causes login to hang (without
falling back to fall-back mode) and in the best case it slows down
login until a network connection is acquired.

Additionally, in order to sanely use these evolution-data-server
libraries, GConf has to be involved and GConf is not thread-safe. So
it's not really feasible just moving the code to a separate
thread. Therefore, move all calendar IO out of process and use a
simple (and private) D-Bus interface for the shell to communicate with
the out-of-process helper.

For simplification, remove existing in-process code since internal
interfaces have been slightly revised. This means that the shell is no
longer using any native code for drawing the calendar dropdown.

https://bugzilla.gnome.org/show_bug.cgi?id=641396

Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
David Zeuthen 2011-02-25 17:42:25 -05:00
parent ddc135839b
commit e9e30138bd
20 changed files with 1365 additions and 2796 deletions

View File

@ -66,10 +66,10 @@ GJS_MIN_VERSION=0.7.11
MUTTER_MIN_VERSION=2.91.90
GTK_MIN_VERSION=3.0.0
GIO_MIN_VERSION=2.25.9
LIBECAL_REQUIRED=1.6.0
LIBEDATASERVER_REQUIRED=1.2.0
LIBEDATASERVERUI2_REQUIRED=1.2.0
LIBEDATASERVERUI3_REQUIRED=2.91.6
LIBECAL_MIN_VERSION=1.6.0
LIBEDATASERVER_MIN_VERSION=1.2.0
LIBEDATASERVERUI2_MIN_VERSION=1.2.0
LIBEDATASERVERUI3_MIN_VERSION=2.91.6
TELEPATHY_GLIB_MIN_VERSION=0.13.12
POLKIT_MIN_VERSION=0.100
@ -125,12 +125,12 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
# Default to libedataserverui-3.0, but allow falling back to 1.2
PKG_CHECK_EXISTS(libedataserverui-3.0,
[EDS_API=3.0
LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI3_REQUIRED],
LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI3_MIN_VERSION],
[EDS_API=1.2
LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI2_REQUIRED])
PKG_CHECK_MODULES(LIBECAL, libecal-1.2 >= $LIBECAL_REQUIRED libedataserver-1.2 >= $LIBEDATASERVER_REQUIRED libedataserverui-$EDS_API >= $LIBEDATASERVERUI_REQUIRED)
AC_SUBST(LIBECAL_CFLAGS)
AC_SUBST(LIBECAL_LIBS)
LIBEDATASERVERUI_MIN_VERSION=$LIBEDATASERVERUI2_MIN_VERSION])
PKG_CHECK_MODULES(CALENDAR_SERVER, libecal-1.2 >= $LIBECAL_MIN_VERSION libedataserver-1.2 >= $LIBEDATASERVER_MIN_VERSION libedataserverui-$EDS_API >= $LIBEDATASERVERUI_MIN_VERSION gio-2.0)
AC_SUBST(CALENDAR_SERVER_CFLAGS)
AC_SUBST(CALENDAR_SERVER_LIBS)
MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
# FIXME: metacity-plugins.pc should point directly to its .gir file

View File

@ -1,5 +1,6 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const DBus = imports.dbus;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
@ -158,13 +159,14 @@ function _getEventDayAbbreviation(dayNumber) {
// Abstraction for an appointment/event in a calendar
function CalendarEvent(date, summary, allDay) {
this._init(date, summary, allDay);
function CalendarEvent(date, end, summary, allDay) {
this._init(date, end, summary, allDay);
}
CalendarEvent.prototype = {
_init: function(date, summary, allDay) {
_init: function(date, end, summary, allDay) {
this.date = date;
this.end = end;
this.summary = summary;
this.allDay = allDay;
}
@ -196,139 +198,136 @@ EmptyEventSource.prototype = {
};
Signals.addSignalMethods(EmptyEventSource.prototype);
// Second, wrap native Evolution event source
function EvolutionEventSource() {
this._init();
}
EvolutionEventSource.prototype = {
_init: function() {
this._native = new Shell.EvolutionEventSource();
this._native.connect('changed', Lang.bind(this, function() {
this.emit('changed');
}));
},
requestRange: function(begin, end) {
this._native.request_range(begin.getTime(), end.getTime());
},
getEvents: function(begin, end) {
let result = [];
let nativeEvents = this._native.get_events(begin.getTime(), end.getTime());
for (let n = 0; n < nativeEvents.length; n++) {
let nativeEvent = nativeEvents[n];
result.push(new CalendarEvent(new Date(nativeEvent.msec_begin), nativeEvent.summary, nativeEvent.all_day));
}
return result;
},
hasEvents: function(day) {
let dayBegin = _getBeginningOfDay(day);
let dayEnd = _getEndOfDay(day);
let events = this.getEvents(dayBegin, dayEnd);
if (events.length == 0)
return false;
return true;
}
const CalendarServerIface = {
name: 'org.gnome.Shell.CalendarServer',
methods: [{ name: 'GetEvents',
inSignature: 'xxb',
outSignature: 'a(sssbxxa{sv})' }],
signals: [{ name: 'Changed',
inSignature: '' }]
};
Signals.addSignalMethods(EvolutionEventSource.prototype);
// Finally, an implementation with fake events
function FakeEventSource() {
const CalendarServer = function () {
this._init();
};
CalendarServer.prototype = {
_init: function() {
DBus.session.proxifyObject(this, 'org.gnome.Shell.CalendarServer', '/org/gnome/Shell/CalendarServer');
}
};
DBus.proxifyPrototype(CalendarServer.prototype, CalendarServerIface);
// an implementation that reads data from a session bus service
function DBusEventSource(owner) {
this._init(owner);
}
FakeEventSource.prototype = {
_init: function() {
function _datesEqual(a, b) {
if (a < b)
return false;
else if (a > b)
return false;
return true;
}
this._fakeEvents = [];
function _dateIntervalsOverlap(a0, a1, b0, b1)
{
if (a1 <= b0)
return false;
else if (b1 <= a0)
return false;
else
return true;
}
// Generate fake events
//
let midnightToday = _getBeginningOfDay(new Date());
let summary = '';
// '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am
for (let n = 0; n < 10; n++) {
let t = new Date(midnightToday.getTime() - n * 4 * 86400 * 1000);
t.setHours(10);
summary = '10-oclock pow-wow (n=' + n + ')';
this._fakeEvents.push(new CalendarEvent(t, summary, false));
}
DBusEventSource.prototype = {
_init: function(owner) {
this._resetCache();
// '11-oclock thing' is an event occuring every three days at 11am
for (let n = 0; n < 10; n++) {
let t = new Date(midnightToday.getTime() + n * 3 * 86400 * 1000);
t.setHours(11);
summary = '11-oclock thing (n=' + n + ')';
this._fakeEvents.push(new CalendarEvent(t, summary, false));
}
this._dbusProxy = new CalendarServer(owner);
this._dbusProxy.connect('Changed', Lang.bind(this, this._onChanged));
// 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced)
for (let n = 0; n < 5; n++) {
let t = new Date(midnightToday.getTime() + (n * 7 + 2) * 86400 * 1000);
t.setHours(13);
t.setMinutes(45);
summary = 'Weekly Meeting (n=' + n + ')';
this._fakeEvents.push(new CalendarEvent(t, summary, false));
}
// 'Fun All Day' is an all-day event occuring every fortnight (three days displayed)
for (let n = 0; n < 10; n++) {
let t = new Date(midnightToday.getTime() + (n * 14 + 3) * 86400 * 1000);
summary = 'Fun All Day (n=' + n + ')';
this._fakeEvents.push(new CalendarEvent(t, summary, true));
}
// 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-)
this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 4, 16, 0), 'Get Married', false));
// ditto for 'NE Patriots vs NY Jets'
this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets', false));
// An event for tomorrow @6:30pm that is added/removed every five
// seconds (to check that the ::changed signal works)
let transientEventDate = new Date(midnightToday.getTime() + 86400 * 1000);
transientEventDate.setHours(18);
transientEventDate.setMinutes(30);
transientEventDate.setSeconds(0);
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
this._includeTransientEvent = false;
this._transientEvent = new CalendarEvent(transientEventDate, 'A Transient Event', false);
this._transientEventCounter = 1;
DBus.session.watch_name('org.gnome.Shell.CalendarServer',
false, // do not launch a name-owner if none exists
Lang.bind(this, this._onNameAppeared),
Lang.bind(this, this._onNameVanished));
},
_updateTransientEvent: function() {
this._includeTransientEvent = !this._includeTransientEvent;
this._transientEventCounter = this._transientEventCounter + 1;
this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')';
_resetCache: function() {
this._events = [];
this._lastRequestBegin = null;
this._lastRequestEnd = null;
},
_onNameAppeared: function(owner) {
this._resetCache();
this._loadEvents(true);
},
_onNameVanished: function(oldOwner) {
this._resetCache();
this.emit('changed');
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
},
requestRange: function(begin, end) {
_onChanged: function() {
this._loadEvents(false);
},
_onEventsReceived: function(appointments) {
let newEvents = [];
if (appointments != null) {
for (let n = 0; n < appointments.length; n++) {
let a = appointments[n];
let date = new Date(a[4] * 1000);
let end = new Date(a[5] * 1000);
let summary = a[1];
let allDay = a[3];
let event = new CalendarEvent(date, end, summary, allDay);
newEvents.push(event);
}
newEvents.sort(function(event1, event2) {
return event1.date.getTime() - event2.date.getTime();
});
}
this._events = newEvents;
this.emit('changed');
},
_loadEvents: function(forceReload) {
if (this._curRequestBegin && this._curRequestEnd){
let callFlags = 0;
if (forceReload)
callFlags |= DBus.CALL_FLAG_START;
this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000,
this._curRequestEnd.getTime() / 1000,
forceReload,
Lang.bind(this, this._onEventsReceived),
callFlags);
}
},
requestRange: function(begin, end, forceReload) {
if (forceReload || !(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
this._lastRequestBegin = begin;
this._lastRequestEnd = end;
this._curRequestBegin = begin;
this._curRequestEnd = end;
this._loadEvents(forceReload);
}
},
getEvents: function(begin, end) {
let result = [];
//log('begin:' + begin);
//log('end: ' + end);
for(let n = 0; n < this._fakeEvents.length; n++) {
let event = this._fakeEvents[n];
if (event.date >= begin && event.date <= end) {
for(let n = 0; n < this._events.length; n++) {
let event = this._events[n];
if (_dateIntervalsOverlap (event.date, event.end, begin, end)) {
result.push(event);
}
//log('when:' + event.date + ' summary:' + event.summary);
}
if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
result.push(this._transientEvent);
result.sort(function(event1, event2) {
return event1.date.getTime() - event2.date.getTime();
});
return result;
},
@ -343,9 +342,8 @@ FakeEventSource.prototype = {
return true;
}
};
Signals.addSignalMethods(FakeEventSource.prototype);
Signals.addSignalMethods(DBusEventSource.prototype);
// Calendar:
// @eventSource: is an object implementing the EventSource API, e.g. the
@ -358,7 +356,10 @@ Calendar.prototype = {
_init: function(eventSource) {
this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, this._update));
this._eventSource.connect('changed', Lang.bind(this,
function() {
this._update(false);
}));
// FIXME: This is actually the fallback method for GTK+ for the week start;
// GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably
@ -407,15 +408,17 @@ Calendar.prototype = {
Lang.bind(this, this._onScroll));
this._buildHeader ();
this._update();
},
// Sets the calendar to show a specific date
setDate: function(date) {
setDate: function(date, forceReload) {
if (!_sameDay(date, this._selectedDate)) {
this._selectedDate = date;
this._update();
this._update(forceReload);
this.emit('selected-date-changed', new Date(this._selectedDate));
} else {
if (forceReload)
this._update(forceReload);
}
},
@ -510,7 +513,7 @@ Calendar.prototype = {
}
}
this.setDate(newDate);
this.setDate(newDate, false);
},
_onNextMonthButtonClicked: function() {
@ -532,16 +535,16 @@ Calendar.prototype = {
}
}
this.setDate(newDate);
this.setDate(newDate, false);
},
_onSettingsChange: function() {
this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
this._buildHeader();
this._update();
this._update(false);
},
_update: function() {
_update: function(forceReload) {
let now = new Date();
if (_sameYear(this._selectedDate, now))
@ -570,7 +573,7 @@ Calendar.prototype = {
let iterStr = iter.toUTCString();
button.connect('clicked', Lang.bind(this, function() {
let newlySelectedDate = new Date(iterStr);
this.setDate(newlySelectedDate);
this.setDate(newlySelectedDate, false);
}));
let hasEvents = this._eventSource.hasEvents(iter);
@ -620,7 +623,7 @@ Calendar.prototype = {
}
// Signal to the event source that we are interested in events
// only from this date range
this._eventSource.requestRange(beginDate, iter);
this._eventSource.requestRange(beginDate, iter, forceReload);
}
};
@ -698,7 +701,7 @@ EventsList.prototype = {
if (events.length == 0 && showNothingScheduled) {
let now = new Date();
/* Translators: Text to show if there are no events */
let nothingEvent = new CalendarEvent(now, _("Nothing Scheduled"), true);
let nothingEvent = new CalendarEvent(now, now, _("Nothing Scheduled"), true);
let timeString = _formatEventTime(nothingEvent, clockFormat);
this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
}

View File

@ -52,9 +52,7 @@ DateMenuButton.prototype = {
let hbox;
let vbox;
//this._eventSource = new Calendar.EmptyEventSource();
//this._eventSource = new Calendar.FakeEventSource();
this._eventSource = new Calendar.EvolutionEventSource();
this._eventSource = new Calendar.DBusEventSource();
let menuAlignment = 0.25;
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
@ -117,7 +115,20 @@ DateMenuButton.prototype = {
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
if (isOpen) {
let now = new Date();
this._calendar.setDate(now);
/* Passing true to setDate() forces events to be reloaded. We
* want this behavior, because
*
* o It will cause activation of the calendar server which is
* useful if it has crashed
*
* o It will cause the calendar server to reload events which
* is useful if dynamic updates are not supported or not
* properly working
*
* Since this only happens when the menu is opened, the cost
* isn't very big.
*/
this._calendar.setDate(now, true);
// No need to update this._eventList as ::selected-date-changed
// signal will fire
}

View File

@ -95,6 +95,10 @@ function start() {
// back into sync ones.
DBus.session.flush();
// Load the calendar server. Note that we are careful about
// not loading any events until the user presses the clock
global.launch_calendar_server();
Environment.init();
// Ensure ShellWindowTracker and ShellAppUsage are initialized; this will

View File

@ -1,22 +0,0 @@
noinst_LTLIBRARIES += libcalendar-client.la
libcalendar_client_la_SOURCES = \
calendar-client/calendar-client.h calendar-client/calendar-client.c \
calendar-client/calendar-debug.h \
calendar-client/calendar-sources.c calendar-client/calendar-sources.h \
$(NULL)
libcalendar_client_la_CFLAGS = \
-I$(top_srcdir)/src \
-DPREFIX=\""$(prefix)"\" \
-DLIBDIR=\""$(libdir)"\" \
-DDATADIR=\""$(datadir)"\" \
-DG_DISABLE_DEPRECATED \
-DG_LOG_DOMAIN=\"CalendarClient\" \
$(LIBECAL_CFLAGS) \
$(NULL)
libcalendar_client_la_LIBADD = $(LIBECAL_LIBS)
EXTRA_DIST += calendar-client/README

View File

@ -0,0 +1,34 @@
servicedir = $(datadir)/dbus-1/services
service_in_files = calendar-server/org.gnome.Shell.CalendarServer.service.in
service_DATA = $(service_in_files:.service.in=.service)
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@.tmp && mv $@.tmp $@
libexec_PROGRAMS += gnome-shell-calendar-server
gnome_shell_calendar_server_SOURCES = \
calendar-server/calendar-debug.h \
calendar-server/calendar-sources.c calendar-server/calendar-sources.h \
calendar-server/gnome-shell-calendar-server.c \
$(NULL)
gnome_shell_calendar_server_CFLAGS = \
-I$(top_srcdir)/src \
-DPREFIX=\""$(prefix)"\" \
-DLIBDIR=\""$(libdir)"\" \
-DDATADIR=\""$(datadir)"\" \
-DG_DISABLE_DEPRECATED \
-DG_LOG_DOMAIN=\"ShellCalendarServer\" \
$(CALENDAR_SERVER_CFLAGS) \
$(NULL)
gnome_shell_calendar_server_LDFLAGS = \
$(CALENDAR_SERVER_LIBS) \
$(NULL)
EXTRA_DIST += \
calendar-server/README \
calendar-server/org.gnome.Shell.CalendarServer.service.in \
$(NULL)

View File

@ -27,13 +27,14 @@ include Makefile-gdmuser.am
include Makefile-st.am
include Makefile-tray.am
include Makefile-gvc.am
include Makefile-calendar-client.am
include Makefile-calendar-server.am
gnome_shell_cflags = \
$(MUTTER_PLUGIN_CFLAGS) \
$(LIBGNOMEUI_CFLAGS) \
-I$(srcdir)/tray \
-DLOCALEDIR=\"$(datadir)/locale\" \
-DGNOME_SHELL_LIBEXECDIR=\"$(libexecdir)\" \
-DGNOME_SHELL_DATADIR=\"$(pkgdatadir)\" \
-DGNOME_SHELL_PKGLIBDIR=\"$(pkglibdir)\" \
-DJSDIR=\"$(pkgdatadir)/js\"
@ -88,8 +89,6 @@ libgnome_shell_la_SOURCES = \
shell-arrow.c \
shell-doc-system.c \
shell-embedded-window.c \
shell-evolution-event-source.h \
shell-evolution-event-source.c \
shell-generic-container.c \
shell-gtk-embed.c \
shell-global.c \
@ -216,10 +215,9 @@ libgnome_shell_la_LIBADD = \
libgdmuser-1.0.la \
libtray.la \
libgvc.la \
libcalendar-client.la \
$(NULL)
libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) $(LIBECAL_CFLAGS)
typelibdir = $(pkglibdir)
typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib Gvc-1.0.typelib

File diff suppressed because it is too large Load Diff

View File

@ -1,149 +0,0 @@
/*
* Copyright (C) 2004 Free Software Foundation, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*/
#ifndef __CALENDAR_CLIENT_H__
#define __CALENDAR_CLIENT_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef enum
{
CALENDAR_EVENT_APPOINTMENT = 1 << 0,
CALENDAR_EVENT_TASK = 1 << 1,
CALENDAR_EVENT_ALL = (1 << 2) - 1
} CalendarEventType;
#define CALENDAR_TYPE_CLIENT (calendar_client_get_type ())
#define CALENDAR_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient))
#define CALENDAR_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass))
#define CALENDAR_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT))
#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT))
#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass))
typedef struct _CalendarClient CalendarClient;
typedef struct _CalendarClientClass CalendarClientClass;
typedef struct _CalendarClientPrivate CalendarClientPrivate;
struct _CalendarClient
{
GObject parent;
CalendarClientPrivate *priv;
};
struct _CalendarClientClass
{
GObjectClass parent_class;
void (* appointments_changed) (CalendarClient *client);
void (* tasks_changed) (CalendarClient *client);
};
typedef struct
{
time_t start_time;
time_t end_time;
} CalendarOccurrence;
typedef struct
{
char *uid;
char *rid;
char *uri;
char *summary;
char *description;
char *color_string;
time_t start_time;
time_t end_time;
guint is_all_day : 1;
/* Only used internally */
GSList *occurrences;
} CalendarAppointment;
typedef struct
{
char *uid;
char *summary;
char *description;
char *color_string;
char *url;
time_t start_time;
time_t due_time;
guint percent_complete;
time_t completed_time;
int priority;
} CalendarTask;
typedef struct
{
union
{
CalendarAppointment appointment;
CalendarTask task;
} event;
CalendarEventType type;
} CalendarEvent;
#define CALENDAR_EVENT(e) ((CalendarEvent *)(e))
#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e))
#define CALENDAR_TASK(e) ((CalendarTask *)(e))
typedef void (* CalendarDayIter) (CalendarClient *client,
guint day,
gpointer user_data);
GType calendar_client_get_type (void) G_GNUC_CONST;
CalendarClient *calendar_client_new (void);
void calendar_client_get_date (CalendarClient *client,
guint *year,
guint *month,
guint *day);
void calendar_client_select_month (CalendarClient *client,
guint month,
guint year);
void calendar_client_select_day (CalendarClient *client,
guint day);
GSList *calendar_client_get_events (CalendarClient *client,
CalendarEventType event_mask);
void calendar_client_foreach_appointment_day (CalendarClient *client,
CalendarDayIter iter_func,
gpointer user_data);
void calendar_client_set_task_completed (CalendarClient *client,
char *task_uid,
gboolean task_completed,
guint percent_complete);
void calendar_event_free (CalendarEvent *event);
G_END_DECLS
#endif /* __CALENDAR_CLIENT_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
[D-BUS Service]
Name=org.gnome.Shell.CalendarServer
Exec=@libexecdir@/gnome-shell-calendar-server

View File

@ -197,7 +197,8 @@ def start_shell(perf_output=None):
if running_from_source_tree:
if os.environ.has_key('GI_TYPELIB_PATH'):
typelib_dir = typelib_dir + ":" + os.environ.get('GI_TYPELIB_PATH')
env.update({'GNOME_SHELL_DATADIR' : data_dir,
env.update({'GNOME_SHELL_BINDIR' : bin_dir,
'GNOME_SHELL_DATADIR' : data_dir,
'GI_TYPELIB_PATH' : typelib_dir,
'GSETTINGS_SCHEMA_DIR' : data_dir })
else:

View File

@ -1,265 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "config.h"
#include "calendar-client/calendar-client.h"
#include "shell-evolution-event-source.h"
struct _ShellEvolutionEventSourceClass
{
GObjectClass parent_class;
};
struct _ShellEvolutionEventSource {
GObject parent;
CalendarClient *client;
/* The month that we are currently requesting events from */
gint req_year;
gint req_mon; /* starts at 1, not zero */
};
/* Signals */
enum
{
CHANGED_SIGNAL,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (ShellEvolutionEventSource, shell_evolution_event_source, G_TYPE_OBJECT);
static void
on_tasks_changed (CalendarClient *client,
gpointer user_data)
{
ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
/* g_print ("on tasks changed\n"); */
g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
}
static void
on_appointments_changed (CalendarClient *client,
gpointer user_data)
{
ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
/* g_print ("on appointments changed\n"); */
g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
}
static void
shell_evolution_event_source_init (ShellEvolutionEventSource *source)
{
source->client = calendar_client_new ();
g_signal_connect (source->client,
"tasks-changed",
G_CALLBACK (on_tasks_changed),
source);
g_signal_connect (source->client,
"appointments-changed",
G_CALLBACK (on_appointments_changed),
source);
}
static void
shell_evolution_event_source_finalize (GObject *object)
{
ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (object);
g_object_unref (source->client);
G_OBJECT_CLASS (shell_evolution_event_source_parent_class)->finalize (object);
}
static void
shell_evolution_event_source_class_init (ShellEvolutionEventSourceClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = shell_evolution_event_source_finalize;
signals[CHANGED_SIGNAL] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
ShellEvolutionEventSource *
shell_evolution_event_source_new (void)
{
return SHELL_EVOLUTION_EVENT_SOURCE (g_object_new (SHELL_TYPE_EVOLUTION_EVENT_SOURCE, NULL));
}
void
shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end)
{
GDateTime *middle_utc, *middle;
/* The CalendarClient type is a convenience wrapper on top of
* Evolution Data Server. It is based on the assumption that only
* a single month is shown at a time.
*
* To avoid reimplemting all the work already done in CalendarClient
* we make the same assumption. This means that we only show events
* in the month that is in the middle of @msec_begin and
* @msec_end. Since the Shell displays a month at a time (plus the
* days before and after) it works out just fine.
*/
middle_utc = g_date_time_new_from_unix_utc ((msec_begin + msec_end) / 2 / 1000);
/* CalendarClient uses localtime rather than UTC */
middle = g_date_time_to_local (middle_utc);
g_date_time_unref (middle_utc);
g_date_time_get_ymd (middle, &source->req_year, &source->req_mon, NULL);
g_date_time_unref (middle);
calendar_client_select_month (source->client, source->req_mon - 1, source->req_year);
}
static gint
event_cmp (gconstpointer a,
gconstpointer b)
{
const ShellEvolutionEvent *ea;
const ShellEvolutionEvent *eb;
ea = a;
eb = b;
if (ea->msec_begin < eb->msec_begin)
return -1;
else if (ea->msec_begin > eb->msec_begin)
return 1;
else
return 0;
}
/**
* shell_evolution_event_source_get_events:
* @source: A #ShellEvolutionEventSource.
* @msec_begin: Start date (milli-seconds since Epoch).
* @msec_end: End date (milli-seconds since Epoch).
*
* Gets all events that occur between @msec_begin and @msec_end.
*
* Returns: (element-type ShellEvolutionEvent) (transfer full): List of events.
*/
GList *
shell_evolution_event_source_get_events (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end)
{
GList *result;
GDateTime *cur_date;
GDateTime *begin_date_utc, *begin_date;
GDateTime *end_date_utc, *end_date;
g_return_val_if_fail (msec_begin <= msec_end, NULL);
result = NULL;
begin_date_utc = g_date_time_new_from_unix_utc (msec_begin / 1000);
end_date_utc = g_date_time_new_from_unix_utc (msec_end / 1000);
/* CalendarClient uses localtime rather than UTC */
begin_date = g_date_time_to_local (begin_date_utc);
end_date = g_date_time_to_local (end_date_utc);
g_date_time_unref (begin_date_utc);
g_date_time_unref (end_date_utc);
cur_date = g_date_time_ref (begin_date);
do
{
gint year, mon, day;
GDateTime *next_date;
g_date_time_get_ymd (cur_date, &year, &mon, &day);
/* g_print ("y=%04d m=%02d d=%02d\n", year, mon, day); */
/* Silently drop events not in range (see comment in
* shell_evolution_event_source_request_range() above)
*/
if (!(year == source->req_year && mon == source->req_mon))
{
/* g_print ("skipping day\n"); */
}
else
{
GSList *events;
GSList *l;
calendar_client_select_day (source->client, day);
events = calendar_client_get_events (source->client, CALENDAR_EVENT_APPOINTMENT);
/* g_print ("num_events: %d\n", g_slist_length (events)); */
for (l = events; l; l = l->next)
{
CalendarAppointment *appointment = l->data;
ShellEvolutionEvent *event;
gint64 start_time;
if (appointment->is_all_day)
{
start_time = g_date_time_to_unix (cur_date) * G_GINT64_CONSTANT (1000);
}
else
{
start_time = appointment->start_time * G_GINT64_CONSTANT (1000);
}
event = shell_evolution_event_new (appointment->summary,
appointment->is_all_day,
start_time);
result = g_list_prepend (result, event);
}
g_slist_foreach (events, (GFunc) calendar_event_free, NULL);
g_slist_free (events);
}
next_date = g_date_time_add_days (cur_date, 1);
g_date_time_unref (cur_date);
cur_date = next_date;
}
while (g_date_time_difference (end_date, cur_date) > 0);
g_date_time_unref (begin_date);
g_date_time_unref (end_date);
result = g_list_sort (result, event_cmp);
return result;
}
G_DEFINE_BOXED_TYPE (ShellEvolutionEvent,
shell_evolution_event,
shell_evolution_event_copy,
shell_evolution_event_free);
void
shell_evolution_event_free (ShellEvolutionEvent *event)
{
g_free (event->summary);
g_free (event);
}
ShellEvolutionEvent *
shell_evolution_event_copy (ShellEvolutionEvent *event)
{
ShellEvolutionEvent *copy;
copy = g_memdup (event, sizeof (ShellEvolutionEvent));
copy->summary = g_strdup (event->summary);
return copy;
}
ShellEvolutionEvent *
shell_evolution_event_new (const gchar *summary,
gboolean all_day,
gint64 msec_begin)
{
ShellEvolutionEvent *event;
event = g_new0 (ShellEvolutionEvent, 1);
event->summary = g_strdup (summary);
event->all_day = all_day;
event->msec_begin = msec_begin;
return event;
}

View File

@ -1,45 +0,0 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __SHELL_EVOLUTION_EVENT_SOURCE_H__
#define __SHELL_EVOLUTION_EVENT_SOURCE_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef struct _ShellEvolutionEvent ShellEvolutionEvent;
struct _ShellEvolutionEvent
{
gchar *summary;
gboolean all_day;
gint64 msec_begin;
};
GType shell_evolution_event_get_type (void) G_GNUC_CONST;
ShellEvolutionEvent *shell_evolution_event_new (const gchar *summary,
gboolean all_day,
gint64 msec_begin);
ShellEvolutionEvent *shell_evolution_event_copy (ShellEvolutionEvent *event);
void shell_evolution_event_free (ShellEvolutionEvent *event);
typedef struct _ShellEvolutionEventSource ShellEvolutionEventSource;
typedef struct _ShellEvolutionEventSourceClass ShellEvolutionEventSourceClass;
#define SHELL_TYPE_EVOLUTION_EVENT_SOURCE (shell_evolution_event_source_get_type ())
#define SHELL_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSource))
#define SHELL_EVOLUTION_EVENT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
#define SHELL_IS_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
#define SHELL_IS_EVOLUTION_EVENT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
#define SHELL_EVOLUTION_EVENT_SOURCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
GType shell_evolution_event_source_get_type (void) G_GNUC_CONST;
ShellEvolutionEventSource *shell_evolution_event_source_new (void);
void shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end);
GList *shell_evolution_event_source_get_events (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end);
G_END_DECLS
#endif /* __SHELL_EVOLUTION_EVENT_SOURCE_H__ */

View File

@ -1984,3 +1984,57 @@ shell_get_tp_contacts (TpConnection *self,
shell_global_get_tp_contacts_cb,
callback, NULL, NULL);
}
/**
* shell_global_launch_calendar_server:
* @global: The #ShellGlobal.
*
* Launch the gnome-shell-calendar-server helper.
*/
void
shell_global_launch_calendar_server (ShellGlobal *global)
{
const gchar *bin_dir;
gchar *calendar_server_exe;
GError *error;
gchar *argv[2];
gint child_standard_input;
/* launch calendar-server */
bin_dir = g_getenv ("GNOME_SHELL_BINDIR");
if (bin_dir != NULL)
calendar_server_exe = g_strdup_printf ("%s/gnome-shell-calendar-server", bin_dir);
else
calendar_server_exe = g_strdup_printf (GNOME_SHELL_LIBEXECDIR "/gnome-shell-calendar-server");
argv[0] = calendar_server_exe;
argv[1] = NULL;
error = NULL;
if (!g_spawn_async_with_pipes (NULL, /* working_directory */
argv,
NULL, /* envp */
0, /* GSpawnFlags */
NULL, /* child_setup */
NULL, /* user_data */
NULL, /* GPid *child_pid */
&child_standard_input,
NULL, /* gint *stdout */
NULL, /* gint *stderr */
&error))
{
g_warning ("Error launching `%s': %s (%s %d)",
calendar_server_exe,
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
}
/* Note that gnome-shell-calendar-server exits whenever its stdin
* file descriptor is HUP'ed. This means that whenever the the shell
* process exits or is being replaced, the calendar server is also
* exits...and if the shell is being replaced, a new copy of the
* calendar server is launched...
*/
g_free (calendar_server_exe);
}

View File

@ -156,6 +156,8 @@ void shell_get_tp_contacts (TpConnection *self,
const TpContactFeature *features,
ShellGetTpContactCb callback);
void shell_global_launch_calendar_server (ShellGlobal *global);
G_END_DECLS
#endif /* __SHELL_GLOBAL_H__ */