Coding own application for Gnome-shell Calendar / Evolution calendar
I'm looking to write a calendar application in python that will interact with gnome-shell-calendar
. I've asked around, and I was told that it uses evolution-data-sever
To get its information, I found out that there is a python-evolution
python module that allows you to interact with the evolution server. But, that module has now been depreciated. Is-there another way to interact with the sever?
I've also noticed a process called gnome-shell-calendar-server
. What's The difference between that and the evolution one?
Solution 1:
Evolution Data Server 3.6 can be accessed with Python using gobject introspection. For this, gir1.2-edataserver-1.2
and gir1-2-ecalendar-1.2
also need to be installed.
For example, the following script will list all the events in all calendars in evolution-data-server.
#! /usr/bin/python
# -*- coding: utf-8 -*-
from gi.repository import ECalendar
from gi.repository import EDataServer
# Open a registry and get a list of all the calendars in EDS
registry = EDataServer.SourceRegistry.new_sync(None)
sources = EDataServer.SourceRegistry.list_sources(registry, EDataServer.SOURCE_EXTENSION_CALENDAR)
# Open each calendar containing events and get a list of all objects in them
for source in sources:
client = ECalendar.CalClient.new(source, ECalendar.CalSourceType.EVENT)
client.open_sync(False, None)
# ret is true or false depending if events are found or not
# values is a list of events
ret, values = client.get_object_list_as_comps_sync("#t", None)
if ret:
for value in values:
event = value.get_as_string()
print event
Solution 2:
Mark's answer was probably good in 2012, but it's now a bit outdated. I wanted to play with this and managed to do it after digging on my own.
Packages
This was tested on Ubuntu 20.04 with the following packages installed:
sudo apt update
sudo apt install -y \
gir1.2-ecalendar-1.2 \
gir1.2-ecal-2.0
Conda environment setup (optional)
If you use your system's python, you'll only need pgi
pip package. Here's how to get it working with Conda:
- I used
conda
to create my virtual env so I installedpgi
pip package - I installed
libgirepository
andpygobject
using conda-forge. - I symlinked required typelib files from
gir1.2-ecalendar-1.2
andgir1.2-ecal-2.0
to my conda env'sgirepository
's folder exactly like this:
#!/usr/bin/env bash
set -ex
girepository_source=/usr/lib/x86_64-linux-gnu/girepository-1.0
conda_env_name=evolution-calendar-playground
conda_girespository_destination=${CONDA_PREFIX}/envs/${conda_env_name}/lib/girepository-1.0/
# setup conda environment
conda activate ${conda_env_name}
pip install pgi
conda install -c conda-forge libgirepository -y
conda install -c conda-forge pygobject -y
# compare the girepository folders
diff -rq "${girepository_source}" "${conda_girespository_destination}"
# link only required typelib files
ln -s "${girepository_source}/EDataServer-1.2.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/Soup-2.4.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/Camel-1.2.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/GData-0.0.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/Json-1.0.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/Goa-1.0.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/ECal-2.0.typelib" "${conda_girespository_destination}"
ln -s "${girepository_source}/ICalGLib-3.0.typelib" "${conda_girespository_destination}"
# alternative version of the above, link all files that are missing
typelibs_not_in_conda_env=$(
diff -rq "${girepository_source}" "${conda_girespository_destination}" \
| grep "Only in ${girepository_source}" \
| cut -d " " -f 4-
| tr "\n" " "
)
for typelib in $(echo ${typelibs_not_in_conda_env} | tr " " "\n"); do
ln -s "${girepository_source}/${typelib}" "${conda_girespository_destination}"
done
# compare the girepository folders (again)
diff -rq "${girepository_source}" "${conda_girespository_destination}"
Updated python example to retrieve all events from all calendars in Evolution Data Server
Here's an updated python script that uses gobject introspection (it's highly based on Mark's example). It does the same as before, but written differently and updated to use gir1.2-ecal-2.0
instead of gir1.2-ecalendar-1.2
.
I've linked some documentation to make it easier to know what's going on and added a bunch of print
statements since these libraries are dynamically loaded and it's a bit harder to get intellicense on them. Feel free to cleanup everything once you have what you need.
Also, according to the docs, I think some of the connections I open should be cleaned and I'm not doing it, but you should :)
import gi
gi.require_version('EDataServer', '1.2')
from gi.repository import EDataServer
gi.require_version('ECal', '2.0')
from gi.repository import ECal
from gi.repository import Gio
# https://lazka.github.io/pgi-docs/Gio-2.0/classes/Cancellable.html#Gio.Cancellable
GIO_CANCELLABLE = Gio.Cancellable.new()
class EvolutionCalendarWrapper:
@staticmethod
def _get_gnome_calendars():
# https://lazka.github.io/pgi-docs/EDataServer-1.2/classes/SourceRegistry.html#EDataServer.SourceRegistry.new_sync
registry = EDataServer.SourceRegistry.new_sync(GIO_CANCELLABLE)
return EDataServer.SourceRegistry.list_sources(registry, EDataServer.SOURCE_EXTENSION_CALENDAR)
def _get_gnome_events_from_calendar_source(self, source: EDataServer.Source):
print(source.get_display_name())
# https://lazka.github.io/pgi-docs/ECal-2.0/classes/Client.html#ECal.Client
client = ECal.Client()
# https://lazka.github.io/pgi-docs/ECal-2.0/classes/Client.html#ECal.Client.connect_sync
new_client = client.connect_sync(
source=source,
source_type=ECal.ClientSourceType.EVENTS,
wait_for_connected_seconds=1, # this should probably be configured
cancellable=GIO_CANCELLABLE,
)
if new_client:
events = []
# https://lazka.github.io/pgi-docs/ECal-2.0/classes/Client.html#ECal.Client.get_object_list_as_comps_sync
ret, values = new_client.get_object_list_as_comps_sync(sexp="#t", cancellable=GIO_CANCELLABLE)
if ret:
for value in values:
print(value.get_status())
print(value.get_location())
print(value.get_descriptions())
print("due timestamp:", value.get_due().get_value().as_timet())
start_time_value = value.get_dtstart().get_value()
print("start_date", start_time_value.get_date())
print("start_time", start_time_value.get_time())
print("timestamp:", start_time_value.as_timet())
print("as_timet_with_zone:", start_time_value.as_timet_with_zone())
print("timezone:", start_time_value.get_timezone())
# event_str = value.get_as_string()
# print(event_str)
event = value
events.append(event)
return events
def get_all_events(self):
calendars = self._get_gnome_calendars()
events = []
for source in calendars:
events += self._get_gnome_events_from_calendar_source(source)
return events
def main():
evolutionCalendar = EvolutionCalendarWrapper()
events = evolutionCalendar.get_all_events()
for event in events:
print(event)
if __name__ == "__main__":
main()
Now extra caution with what you do with the events out there as you can also edit them, don't break your calendar ;)