One of the main ideas of GTD is to have a context associated with each task, so that it is very easy to see which tasks can be done in your current context. I organize my tasks with Taskwarrior, so to make it work with contexts, when adding a new task I need to assign a context to it and also update the current context whenever it changes. There are different types of context, but the easiest to automate are the spatial ones, that is, which tasks I can do where I'm at right now.
Assigning a task to a context is as simple as adding a tag to it. I have three
different places I'm normally at, so I either add a @rep
, @sp
or
@uni
tag depending on where the task can be done.
To update the current context though I'd need to manually tell Taskwarrior where
I'm at right now every time I go to any other place. For example, I'd need to
type task context uni
every time I went to the university. This is not only
very boring but also error-prone: more than once it took me a couple seconds to
understand why some tasks were missing.
As any other little problem in life, this can be solved with a little scripting. So that's why I wrote a python script to automatically detect and set the current context for Taskwarrior.
The idea is very simple: I'm almost always connected to the WiFi, since it connects automatically, and each location has specific WiFi network names, so I just need to get the current WiFi SSID and set the corresponding context.
That is what the following python script does (aside from sending a notification
with notify-send
):
import subprocess
contexts = {
'rep': ["rep wifi 1", "rep wifi 2", "rep wifi 3"],
'sp': ["sp wifi 1", "sp wifi 2"],
'uni': ["eduroam"]
}
def get_context(wifi):
for context in contexts:
if wifi in contexts[context]:
return context
return None
def get_current_context():
wifi_cmd = subprocess.run(["iwgetid", "-r"], text=True, capture_output=True)
wifi = wifi_cmd.stdout.split('\n')[0]
return get_context(wifi)
def set_current_context():
context = get_current_context()
if context:
subprocess.run(["notify-send", "Taskwarrior context",
f"Setting context to <b>{context}</b>"])
subprocess.run(["task", "context", context])
else:
subprocess.run(["notify-send", "-u", "critical", "Taskwarrior context",
"Failure to detect context"])
subprocess.run(["task", "context", "none"])
Note: The WiFi names for the rep
and sp
contexts were redacted to
avoid exposure.
Since when I go from one place to the other I always suspend, hibernate or shutdown my notebook, that script only needs to be run after resuming or turning the computer on, which is exactly what the following systemd service is for:
[Unit]
Description=Set taskwarrior context
Wants=network-online.target NetworkManager-wait-online.service
After=network-online.target NetworkManager-wait-online.service hibernate.target suspend.target
[Service]
User=%I
Type=oneshot
Environment=PATH=/usr/bin:/home/nfraprado/ark/code/.path/
Environment=DISPLAY=:0
Environment=XAUTHORITY=%h/.Xauthority
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
ExecStartPre=/usr/bin/sleep 30
ExecStart=/home/nfraprado/ark/code/.path/taskwarrior-update_context
ExecStartPost=
[Install]
WantedBy=multi-user.target
WantedBy=hibernate.target
WantedBy=suspend.target
Details about this service file:
network-online
and wait-online
services supposedly make it wait
for NetworkManager to connect to a network before executing, but from my tests
that wasn't enough, so I ended up adding a 30 second delay as can be seen in
ExecStartPre
.hibernate
and suspend
targets make it run after the computer
resumes or turns on.DISPLAY
, XAUTHORITY
and DBUS_SESSION_BUS_ADDRESS
environment
variables allow the notification to appear.taskwarrior-update_context
basically calls set_current_context()
from
the python script.And that's it! With those two pieces, after I move between two locations and open my notebook the Taskwarrior context is automatically updated, showing me only the tasks relevant to the place I'm at.
As a side note, before that python script, I made a bash script. Even though
running a command in bash is way cleaner than python's subprocess.run()
, I
really despise bash's syntax. I also find it awful to need to define a
array_contains
function (which I copied from some StackOverflow answer).
Anyway, here's the bash script if you're curious:
#!/bin/bash
wifi="$(iwgetid -r)"
declare -a rep=("rep wifi 1" "rep wifi 2" "rep wifi 3")
declare -a sp=("sp wifi 1" "sp wifi 2")
declare -a uni=("eduroam")
array_contains () {
local array="$1[@]"
local seeking=$2
local in=1
for element in "${!array}"; do
if [[ $element == $seeking ]]; then
in=0
break
fi
done
return $in
}
array_contains rep "$wifi" && context=rep
array_contains sp "$wifi" && context=sp
array_contains uni "$wifi" && context=uni
if [ -z "$context" ];then
notify-send -u critical "Taskwarrior context" "Failure to detect context"
task context none >/dev/null 2>&1
else
notify-send "Taskwarrior context" "Setting context to <b>$context</b>"
task context $context >/dev/null 2>&1
fi