1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
|
#+TITLE: khalel
=khalel=: Interacting through Emacs with locally-stored calendars via the
console application =khal= and syncing with remote CalDAV calendars using
=vdirsyncer=.
[[file:screenshot_agenda.png]]
=khalel= allows to access calendars stored in =.ics= files and provides means of
listing existing events, edit them as well as to create new ones largely through
an org-mode interface.
/Please note/: This code is in an early state. =khalel= shares limitations of
=khal= especially when it comes to calendar entry fields that are not handled at
the moment by either =khalel=, =khal= or org-mode's =.ics= exporter. Simple
scheduling of events works fine but do not expect features found in more complex
PIM applications such as invitees or even time zone support.
On the other hand, it is all text files, so hack away! :)
* Why =khal= and =khalel=
=khal= is a simple and fast way to read, create and edit calendar entries. It
does, however, have a limited feature set. If you need to deal with many shared
calendar invites and want to edit events with complicated repeat patterns, this
might not be the right tool. If you have a "simple" CalDAV calendar for your own
needs /or/ just want to see upcoming calendar events pop up in the org-mode
agenda in Emacs, then read ahead. Also check out =khal='s [[https://khal.readthedocs.io/en/latest/index.html#features][features and
limitations in its online documentation]] and the additional list of limitations
below.
Adjusting the output of =khal= slightly, one can quite easily get something that already resembles an
org-mode file (link to the online documentation: [[https://khal.readthedocs.io/en/latest/usage.html][khal usage]]):
#+begin_src bash :results output
khal list --format "* {title} \n <{start-date} {start-time}-{end-time}> \n {location} \n {description}" --day-format "" today 10d
#+end_src
#+RESULTS:
: * DnD mit den Toten Charaktären \n <2021-09-04 21:00-23:00> \n \n
: * DHL \n <2021-09-09 13:00-16:00> \n \n
: * Ge blod \n <2021-09-09 13:00-19:00> \n \n
: * Rebeckas släkt \n <2021-09-11 16:00-19:00> \n \n
: * Plocka 🍄 \n <2021-09-12 -> \n \n
: * IcewindDale DnD \n <2021-09-12 16:00-19:00> \n \n
This is showing the events in the coming 10 days from now. This gives us
something to work with and means we don't have to touch the =.ics= files
directly. =khalel= takes this a step further and imports a configurable range of
events into a nicely-formated org-mode file.
Furthermore, =khalel= provides a wrapper around =khal= and =org-mode= export
functionality to create new events via org-mode capture templates. Even editing
existing events is possible through a console-interface to =khal=.
* Use case
I sync my remote CalDAV calendars using [[https://github.com/pimutils/vdirsyncer][vdirsyncer]] which mirrors all events from
my different calendars locally. This calendar store can be accessed through
=khal= and =khalel=. I am mostly interested in seeing upcoming events in the
org-mode agenda and to quickly create new events with the familiar org-capture
mechanism.
This requires a small chain of steps: most notably, in my setup, events created
through =khalel= require an additional import step to be visible in org-mode.
#+begin_src ditaa :file sync_scheme.png
+-------+ +----------+ +-------+ +-------+ +---------+
| |--->| |--->| |--->| |--->| |
| remote| |vdirsyncer| | khal | |khalel | | org mode|
| |<===| |<===| |===>| |===>| |
+-------+ +----------+ +-------+ +-------+ +---------+
^
:
:
+----------+ +--------+
| | | | --> existing events
| org mode |===>| khalel |
| | | | ==> new events
+----------+ +--------+
#+end_src
#+RESULTS:
[[file:sync_scheme.png]]
Of course, the synchronization and update of the imported org-file can be done
in one go! In fact, within the org-file, clickable links allow to either edit a
given event or sync & update all -- of course, without ever leaving Emacs!
** Features and limitations
Advantages with the approach:
- integrates neatly into my personal, Emacs-centric work-flow relying heavily on org-mode
- easy to customize with some elisp
- local calendar stores can easily be backed up (or even edited)
- synchronization can be done manually (when online) or automatized via cron job
or from within Emacs
Disadvantages:
- several steps requiring different tools
- more initial set-up work
- currently, many features of =.ics=/CalDAV are not supported:
- no timezones
- no organizers/invitees
- ...
- code is still evolving and features might change from one version to the next
Besides calendars, this setup can be extended to contacts using:
- [[https://github.com/pimutils/vdirsyncer][vdirsyncer]] (again) to mirror contacts from CardDAV server locally,
- [[https://github.com/scheibler/khard][khard]] to access them via the command line, and
- [[https://github.com/DamienCassou/khardel][khardel]] for integration into Emacs (not by me).
** Similar packages
- [[https://github.com/dengste/org-caldav][org-caldav]] :: Focuses on synchronizing Org mode files /to/ CalDAV servers where =khalel= mostly imports to and creates events from Org mode. Allows to use more Org mode features but some information might not be transferred to CalDAV. Employs synchronization code running in Emacs instead of using external tools. *Note*: /currently missing maintainer/, see https://github.com/dengste/org-caldav/issues/234
* Getting started
** Installing and configuring =vdirsyncer=
Follow the installation instructions at [[https://github.com/pimutils/vdirsyncer]].
The configuration is explained in detail in the [[http://vdirsyncer.pimutils.org/en/stable/config.html#][online manual]] including a
[[http://vdirsyncer.pimutils.org/en/stable/tutorial.html][tutorial and minimal example]].
When done with the configuration, run =vdirsyncer discover= to test the setup
and then =vdirsyncer sync= to run the synchronization for the first time.
** Installing and configuring =khal=
Simply download the package for your preferred distribution or [[https://khal.readthedocs.io/en/latest/install.html][follow the
installation instructions]]. The latter might be the preferred option, as you need
version =0.10.4= or later.
You can create a configuration interactively by running =khal configure= or
simply use the one below and save it to =~/.config/khal/config=:
#+begin_src conf
[calendars]
[[my_calendar_local]]
path = ~/.calendar/*
type = discover
[locale]
timeformat = %H:%M
dateformat = %Y-%m-%d
longdateformat = %Y-%m-%d %a
datetimeformat = %Y-%m-%d %H:%M
longdatetimeformat = %Y-%m-%d %H:%M
#+end_src
Make sure that the =longdateformat= includes the day of the week in short form
(=%a=) as this makes sure that org-mode recognizes the time stamps correctly
when importing. You can test the settings by running
#+begin_src bash :results output
khal printformats
#+end_src
#+RESULTS:
: longdatetimeformat: 2013-12-21 21:45
: datetimeformat: 2013-12-21 21:45
: longdateformat: 2013-12-21 lör
: dateformat: 2013-12-21
: timeformat: 21:45
The weekday's short form will appear in your configured local language.
You might want to set up a default calendar as well or do that in the =khalel= configuration step below.
** Install =khalel=
The package is available from MELPA: [[https://melpa.org/#/khalel][file:https://melpa.org/packages/khalel-badge.svg]]
Install it through =package-install=.
Alternatively, you can download the source code from [[https://gitlab.com/hperrey/khalel]]
To load the package, I recommend [[https://github.com/jwiegley/use-package][use-package]].
*** Doom Emacs
If you are using [[https://github.com/doomemacs/doomemacs][Doom Emacs]], you can install and load =khalel= by adding
#+begin_src emacs-lisp
(package! khalel)
#+end_src
to your =packages.el= and
#+begin_src emacs-lisp
(use-package! khalel
:after org
:config
(khalel-add-capture-template))
#+end_src
to your =config.el=. Then execute =./doom sync= in the =~/.emacs.d/bin/=
directory to trigger the download of the package.
** Configuring =khalel=
First, make sure that the right =khal= and =vdirsyncer= executables will be used, e.g.
#+begin_src emacs-lisp
(setq khalel-khal-command "~/.local/bin/khal")
(setq khalel-vdirsyncer-command "vdirsyncer")
#+end_src
You might want to customize the values for capture template key and import file for khalel:
#+begin_src emacs-lisp
(setq khalel-capture-key "e")
(setq khalel-import-org-file (concat org-directory "/" "calendar.org"))
#+end_src
=calendar.org= is also in my list of agenda files. There the new events will end up in after the next sync.
*Warning*: =calendar.org= is being overwritten on each import to avoid
collecting duplicates inside the file! The default is therefore to set the file
up in read-only mode. The confirmation prompt for overwriting the file can be
disabled via:
#+begin_src emacs-lisp
(setq khalel-import-org-file-confirm-overwrite nil)
#+end_src
And I never plan too long into the future, so the next 30 days will be more than enough to fill my agenda view:
#+begin_src emacs-lisp
(setq khalel-import-end-date "+30d")
#+end_src
In the same manner, you can set a start date by changing the value of ~khalel-import-start-date~ which defaults to "today". Both variables accept the format supported by ~org-read-date~.
Using these settings, we can now set up a capture template using a helper routine:
#+begin_src emacs-lisp
(khalel-add-capture-template)
#+end_src
Put this call into your Emacs configuration file. The above command will also
register an export hook that is run when the capture is finalized to trigger the
export to =khal=.
** First steps
You can import current events matching the defined date range through
=khalel-import-events= or create new ones through =org-capture= and pressing =e=
(default key) for a new calendar event.
You might want to consider adding the org file with the imported events
(=calendar.org= in the above example) to your org agenda.
If you visit the org file with the imported events, you will notice links below
each event: using these (or by calling =khalel-edit-calendar-event=) you can
edit existing events through =khal= from within Emacs.
To synchronize new, edited or remote events use either the links in the imported
calendar org file or call =khalel-run-vdirsyncer=.
* Tips and tricks
** Creating repeating events
When capturing new events, you can create simple repeating patterns using the
org timestamp syntax with repeater intervals. For example,
#+begin_example
SCHEDULED: <2021-12-07 tis +1w>
#+end_example
sets the corresponding event to repeat every week. See section "Timestamps" in the org manual for more details.
For irregular repeating patterns, you can create several events with the same
basic information by adding further timestamps and ranges to the description
field of the capture template:
#+begin_example
,* example event
SCHEDULED: <2021-11-21 sön 13:27>--<2021-11-21 sön 19:22>
:PROPERTIES:
:CREATED: [2021-11-21 sön 13:27]
:CALENDAR:
:CATEGORY: event
:LOCATION:
:APPT_WARNTIME: 10
:ID: 99c11a2c-bdbd-4625-81b8-4d61729ce64f
:END:
repeats:
- <2021-11-22 mån 17:01-20:01>
- <2021-11-23 tis 19:00>--<2021-11-23 tis 21:21>
#+end_example
For each of the timestamps in the bottom, additional events (with unique IDs)
will be created through the ics export. Please not that the =SCHEDULED=
for the main event is expected to be always present, even when further events
are added as part of the description.
However, with the above approach, each event will carry the repeated time stamps
as part of their description, potentially leading to repeated entries in the Org
agenda view. A cleaner though more verbose approach is using sub-headings:
#+begin_example
,* lecture 1 in example course
SCHEDULED: <2021-11-22 Mon 08:15>--<2021-11-22 Mon 10:00>
:PROPERTIES:
:CREATED: [2021-11-21 sön 13:27]
:CALENDAR: teaching
:CATEGORY: event
:LOCATION: lecture hall A
:APPT_WARNTIME: 10
:ID: 99c11a2c-bdbd-4625-81b8-4d61729ce64f
:END:
,** lecture 2 in example course
SCHEDULED: <2021-11-23 Tue 08:15>--<2021-11-23 Tue 10:00>
,** lecture 3 in example course
SCHEDULED: <2021-11-24 Wed 10:15>--<2021-11-24 Wed 12:00>
:PROPERTIES:
:LOCATION: lecture hall D
:END:
#+end_example
Note that all sub-headings need an individual =SCHEDULED= entry but inherit the
properties of the top entry in the tree (here, for example, the =LOCATION= for
"lecture 2") if not overwritten explicitly. In the export, each sub-tree will be
created as separate calendar event and can even have its own repeat pattern.
** Default calendar
If you are usually only importing into a single calendar, then you can define this as a default calendar:
#+begin_src emacs-lisp
(setq khalel-default-calendar "privat")
#+end_src
This will replace any dialog asking for a calendar to save captures into.
** Limiting import of events from a single calendar
If you call =khal-import-events= with a prefix argument (e.g. =C-u=), the import will be limited to the default calendar defined in =khal-default-calendar=.
** Importing calendars into separate =org= files
If you have several calendars that you would like to import into separate =org= files, you can define your own import routines like this:
#+begin_src emacs-lisp
(defun hanno/import-current-work-events ()
"Import only work events via `khalel-import-events`."
(interactive)
(let ((current-prefix-arg '(4))
(khalel-default-calendar "work")
(khalel-import-org-file (concat org-directory "work-events.org")))
(call-interactively #'khalel-import-events)))
#+end_src
This limits the import to a single calendar =work= and stores it in the file
=work-events.org=. Consider to also modify =khalel-import-org-file-header= and
=khalel-import-format= to make them reflect your customization.
In this configuration, you might want to disable the automatic import when
performing a capture, synchronization or edit, by disabling
=khalel-import-events-after-capture=, =khalel-import-events-after-vdirsyncer=
and =khalel-import-events-after-khal-edit=, respectively.
** Customizing the =org= file that events are imported into
If you want to customize the khalel template, e.g. if you want the scheduled date to be a property, you can do this the following way:
#+begin_src emacs-lisp
(setq khalel-import-format "* {title} {cancelled}\n\
:PROPERTIES:\n:CALENDAR: {calendar}\n\
:LOCATION: {location}\n\
:ID: {uid}\n\
:END:\n\
SCHEDULED: <{start-date-long} {start-time}>--<{end-date-long} {end-time}>\n\
- Description: {description}\n\
- URL: {url}\n- Organizer: {organizer}\n\n\
[[elisp:(khalel-edit-calendar-event)][Edit this event]]\
[[elisp:(progn (khalel-run-vdirsyncer) (khalel-import-events))]\
[Sync and update all]]\n")
#+end_src
You can also change the variable =khalel-import-org-file-header= if you want to change the header of the generated file (e.g. if you want to add a file tag or something like that).
** Integration into mail readers for handling invitations :EXPERIMENTAL:
If you are using =gnus= (or =mu4e= with =gnu='s Article mode) to read mails in Emacs, then there is
now (rudimentary) support for handling iCalendar event invitations. Much of the
functionality is provided by =gnus-icalendar= which will show a summary of the
event and buttons to /accept/ or /decline/ the event. =khalel= taps into this to
add two buttons into any mail with an invitation: /Show Agenda/ and /khal
import/. The former simply shows the Org mode agenda for the day the event is
scheduled while the latter extracts the event and imports it via =khal import=.
- If you are using =mu4e=, you can find more information on the configuration here: https://www.djcbsoftware.nl/code/mu/mu4e/iCalendar.html
- For =gnus=, look into the documentation for =gnus-icalendar=.
After loading and setting up either of the above, the =khal= integration can be enabled by
#+begin_src emacs-lisp
(require 'khalel-icalendar)
#+end_src
* Troubleshooting
** Getting warning message =Ignoring unsafe file local variable: buffer-read-only= when running =khalel-import-events=
The =calendar.org= file in which the current events are imported into, is set
to =read-only-mode= as any changes to this file would be overwritten by the next
import. This is done via so-called "file local variables" which, by default, are
ignored by Emacs until they are marked "safe" by the user.
To mark this particular variable as safe, set the variable
=safe-local-variable-values= in your Emacs configuration, e.g.:
#+begin_src emacs-lisp
(setq safe-local-variable-values
(quote
((buffer-read-only . 1))))
#+end_src
** The file with imported events is empty/contains no scheduled items after running =khal-import-events=
This can have a number of reasons. First check your =khal= installation by running in the terminal:
#+begin_src sh
khal list today 30d
#+end_src
If you get an error message or empty output, please double-check your =khal=
configuration and make sure that you have events scheduled during the next
month.
In case you do get output from the above command but the file =khalel= imports
into is still empty, please check your =*Messages*= buffer for error messages
and continue in the corresponding section.
Also check that you have set ~khalel-import-start-date~ and
~khalel-import-end-date~ to appropriate values.
** =Searching for program: No such file or directory, khal=
This indicates that =khal= could not be found. Run
#+begin_src sh
which khal
#+end_src
and then adjust the variable =khalel-khal-command= to match this path.
** Error message =khal exited with non-zero exit code; see buffer ‘*khal-errors*’ for details.=
As stated in the error message, open the =*khal-errors*= buffer to see the exact
cause of the error.
A likely reason for this error is =khalel= relying on VCALENDAR fields not
supported in the installed =khal= version (e.g. =khal= reporting =critical:
'url'=). Double-check that your version matches the required one:
#+begin_src sh
khal --version
#+end_src
See the above section on =khal= installation for the version requirements.
Should a later version than the above mentioned cause any errors, then please
report this problem and include the version of =khal= and the contents of the
=*khal-errors*= buffer.
** =khal= gives =PytzUsageWarning= messages
This might be a [[https://github.com/pimutils/khal/issues/1092][known issue (#1092)]] in =khal=. One approach is to downgrade to
an earlier version of =tzlocal=:
#+begin_src sh
sudo pip install tzlocal==2
#+end_src
Other options are discussed in the linked issue tracker in case this downgrade
is not suitable for you.
* Reporting issues
Please open an issue on [[https://gitlab.com/hperrey/khalel/-/issues][gitlab]] (preferred) or write an [[mailto:hanno@hoowl.se][email]].
|