Khal to Org - multiday events


Previously I mentioned using Evolution→khal→org-mode to enable me to see my upcoming calendar events in my org-mode agenda. Recently I noticed that multiday events are not handled well - khal is instructed to output the start time for each event, which is always the first day.

Here is an updated script which should be used instead.

#!/usr/bin/env zsh

diary=${HOME}/.emacs.d/diary.org

start=$(date --date='-30days' '+%Y-%m-%d')
end=$(date --date='+30days' '+%Y-%m-%d')

(echo "#+title: Calendar";
 khal list \
      --day-format "DATE={date}" \
      --format "TITLE={title}{repeat-symbol}{alarm-symbol}
TIME={start-end-time-style}
{description}
END" \
      ${start} ${end} | \
     while read i; do
         if [[ ${i} == DATE=* ]]; then
             DATE=${i##DATE=}
         elif [[ ${i} == TITLE=* ]]; then
             TITLE=${i##TITLE=}
         elif [[ ${i} == TIME=* ]]; then
             TIME=${i##TIME=}
         elif [[ ${i} == END ]]; then
             echo "* ${TITLE}"
             echo "<${DATE} ${TIME}>"
             echo "${DESCRIPTION:-}"

             TITLE=""
             TIME=""
             DESCRIPTION=""
         else
             if [[ ${DESCRIPTION} == "" ]]; then
                 DESCRIPTION="$i"
             else
                 DESCRIPTION="${DESCRIPTION}\n$i"
             fi
         fi
     done) > ${diary}

Mail.app random signatures


When using an emacs based mail client for many years, I collected various song lyrics that I particularly enjoyed and automatically added them to my signature on outgoing email. As a result of various life changes I’m consuming a lot less email, and have largely switched to Mail.app, which can cope fine with the reduced volume.

I missed my signatures, though, and it doesn’t seem to be possible to write a Mail.app plugin to get them back with the officially supported APIs from Apple (there are some solutions that monkey patch their way in, but they seem unreliable and regularly break).

Mail.app does support adding random signatures from a list, but maintaining that list by hand is painful. Instead, here is a small elisp script that will initialise the available signatures via Applescript.

It uses the same cookie file format as the source, which was easiest for me, but you could easily switch to something else.

No warranty, please let me know if you have trouble or enjoy it 😃

(require 'cookie1)

;;

(defvar maildotapp-signatures (expand-file-name "~/u/lib/fortune")
  "A file containing quips to use as part of a signature, in a format
understood by the `cookie-snarf' function.")

;;

(defun maildotapp-signature--delete-existing ()
  (ns-do-applescript "
tell application \"Mail\"
     set sigCount to (count of signatures)
     repeat sigCount times
          delete signature 1
     end repeat
end tell
"))

(defun maildotapp-signature--add-sig (name content)
  (ns-do-applescript (format "
tell application \"Mail\"
     make new signature at beginning of signatures with properties {name:\"%s\", content:\"%s\"}
end tell
"
			     name
			     content)))

(defun maildotapp-signature--prepare (quip)
  "Based on a QUIP, prepare a complete signature."
  (concat "dme.\n-- \n" quip))

(defun maildotapp-signature--quote (string)
  "Ensure that STRING is safely quoted."
  ;; XXX dme: This is something, but it is enough?
  (replace-regexp-in-string "\"" "\\\\\"" string))

;;

(defun maildotapp-signature-go! ()
  "Replace all of the signatures in Mail.app with those from
`maildotapp-signatures'.

After running this function, go to Mail.app and set the 'Choose
Signature:' option of relevant accounts to 'At Random'."
  (interactive)

  (maildotapp-signature--delete-existing)

  (let ((signatures (cookie-snarf maildotapp-signatures)))
    (mapcar (lambda (content)
	      (maildotapp-signature--add-sig (maildotapp-signature--quote (substring content 0 (min 30 (length content))))
					     (maildotapp-signature--prepare (maildotapp-signature--quote content))))
	    signatures)))

A small HTTP POST server


A couple of weeks ago Neil mentioned on Mastodon being interested in a privacy focused GPS logger for use in a car, which was odd, as I was thinking about the exact same thing a few days before. RevK jumped in to provide something, which Neil wrote about a short while later. I was lucky enough to be able to buy one of the first versions (everyone can get one now), but was a bit stuck for time to play with it initially, which I’ve been able to remedy over the last couple of days.

It’s a fabulous device, with lots of lovely touches - loads of LEDs used to show which GPS satellites are in use, upload progress, OTA upgrade progress, etc. Really great stuff.

One of the nice features of the firmware is that it will upload traces to an HTTP server of your choice with a POST request. While most HTTP servers support this, it can be a bit of a faff getting things all plumbed together when you just want to test something. To that end, here is a small HTTP POST server in Python that can be used for testing:

#!/usr/bin/env python3

from http.server import BaseHTTPRequestHandler, HTTPServer

class handler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        body = self.rfile.read(content_length)
        self.send_response(200)
        self.end_headers()

        file = self.path[2:]

        with open(file, 'wb') as out:
            out.write(body)
        print(f"Wrote {content_length} bytes to {file}.")

with HTTPServer(('', 8000), handler) as server:
    server.serve_forever()

This listens on port 8000 on the local machine without SSL. Configure your logger to point at it, and it should upload the relevant files (GPX in my case), which are then stored in the current directory:

172.16.100.162 - - [02/Feb/2024 10:24:38] "POST /?logger1-2024-02-01T21-46-06Z.gpx HTTP/1.1" 200 -
Wrote 131661 bytes to logger1-2024-02-01T21-46-06Z.gpx.
172.16.100.162 - - [02/Feb/2024 10:24:52] "POST /?logger1-2024-02-01T22-00-01Z.gpx HTTP/1.1" 200 -
Wrote 30774 bytes to logger1-2024-02-01T22-00-01Z.gpx.

This is not likely to be something that you’d use long term, but it’s convenient for testing.

Next up is to have the server provide access to the uploaded GPX traces using Leaflet maps for visualisation, but that’s for next week.

Evolution calendars in Org Mode's agenda


On a Linux GNOME desktop it’s convenient to use Evolution as a calendar sync tool (for Fastmail in my case). Once a configuration has been created, you can stash away the <mumble>.source files, drop them in place on another machine and (subject to re-entering your password), everything just works.

Such calendars are also available through GNOME Calendar, which is a little easier on the eye for casual glances.

Spending a lot of time in emacs, and enjoying org-mode more all of the time, I’d really like all of my calendar events shown in the org agenda. Achieving that consists of two parts:

  1. getting the events out of the Evolution calendar into a more pliable format,
  2. adding the events to an org file so that they are shown in the agenda.

To simplify the implementation, I use khal as a parser for vCalendar files. It’s definitely the case that you could use vdirsyncer and avoid Evolution, but given that the GUI is sometimes convenient, this way requires configuring only a single sync tool.

Getting events out of Evolution is achieved by grovelling in the sqlite database that Evolution uses as a cache:

#!/usr/bin/env zsh

set -e

calendir=${HOME}/v/calendar
dblist=(
    9ccf564d3aac1c2a02a29be8d44720ec7ea0d5a3
    62870b2ad9e6a1e04b800fea354ae44e6b20f754
    ca1c1bad361ca6e59b57d86f9e06c3fd5ffd4ccc
)

convert_one_calendar ()
{
    # Evolution sqlite db:
    local db=$1
    # Khal directory to populate:
    local dst=$2

    [[ -d ${dst} ]] || mkdir -p ${dst}

    cd ${dst}

    sqlite3 ${db} "select ECacheOBJ from ECacheObjects" |\
	csplit \
	    --quiet \
	    --elide-empty-files \
	    --suffix-format "%03d.ics" \
	    - \
	    '/BEGIN:VEVENT/' '{*}'
}

# Convert each of the Evolution cache databases to a Khal compatible
# directory of .ics files:
#
for db in ${dblist}; do
    rm -r ${calendir}/${db}

    convert_one_calendar \
	${HOME}/.cache/evolution/calendar/${db}/cache.db \
	${calendir}/${db}
done

# Write a khal config to match:

config_dir=${HOME}/.config/khal
config_file=${config_dir}/config

[[ -d ${config_dir} ]] || mkdir ${config_dir}

cat > ${config_file} <<EOF
[calendars]

EOF

for db in ${dblist}; do
    cat >> ${config_file} <<EOF
[[${db}]]
path = ${calendir}/${db}
type = calendar

EOF
done

cat >> ${config_file} <<EOF
[locale]
timeformat = %H:%M
dateformat = %Y-%m-%d
longdateformat = %Y-%m-%d
datetimeformat = %Y-%m-%d %H:%M
longdatetimeformat = %Y-%m-%d %H:%M
EOF

In the script, update the dblist variable to include the list of Evolution calendars that you want to convert - you can see their names in ~/.cache/evolution/calendar. The outputs of the script end up in ~/v/calendar, or update the calendir variable to suit your preferences.

Note that the script will overwrite your current khal configuration file.

Now we have a pile of .ics files and a khal configuration configured to parse them. Using that in the org agenda requires another conversion:

#!/usr/bin/env zsh

diary=${HOME}/.emacs.d/diary.org

start=$(date --date='-30days' '+%Y-%m-%d')
end=$(date --date='+30days' '+%Y-%m-%d')

khal list \
     --day-format "" \
     --format "* {title}{repeat-symbol}{alarm-symbol}
<{start-date} {start-end-time-style}>
{description}" \
     ${start} ${end} \
     > ${diary} \
     2> /dev/null

This one is less thorough - it only includes events for the previous 30 and next 30 days. You could tweak this, of course, but I find it sufficient for awareness in the org agenda.

The script generates ~/.emacs.d/diary.org, which you should ensure to include in org-agenda-files so that the events contained therein are displayed.

That’s it! I run the pair of scripts in sequence every hour, but you can decide what to do about that.

Originally I was generating an emacs diary file, and that works out okay. It means that org-timeblock is unaware of the entries, though, which can sometimes be an inconvenience.

Looking for a new non-iPad tablet


Dear Lazyweb,

I’m looking for recommendations for a non-iPad tablet, which I imagine means Android. The requirements are not very specific - at least 200dpi, around 10", under £300 (would like under £200). Ideally I would be able to run upstream Android, but I realise that this is unlikely to be possible without buying a Google Pixel tablet.

The Amazon Fire HD tablets would probably be fine in most respects, except that I don’t believe that it’s possible to get rid of the Amazon advertising skin any more (I grabbed a refurb Fire HD 10 to try, and the approach using adb to delete packages now fails), and I find it quite annoying.

Something like the HONOR Pad X9 or Lenovo Tab P11 look better, but I don’t see any non-fluff reviews.

Use is ePub (non-Amazon) reading, web browsing, a little light Home Assistant, occasionally video when travelling. No games.

Any suggestions?