DEV Community

loading...

Integrating Task Management with Sway

Kamus Hadenes
Originally published at blog.hadenes.io on ・5 min read

This post is part of a series

In this article, I’ll describe how I integrated my tasks with SwayWM using Wofi.

Introduction

When tasks started pilling up and I constantly found myself in a bad situation because I forgot something had to be done, I went looking for a simple, CLI-based task management system to keep a log of simple items I needed to do.

I found the excellent Taskwarrior. I won’t go into details of how to use it, because it’s website has all this information and honestly it’s pretty simple.

Instead, I’ll focus on describing the custom scripts I developed to quickly interact with my tasks with a few keystrokes.

Check the video below to understand what I’m talking about.

%[https://youtu.be/8Y69oHxUNzk]

Screenshots

2021-02-28T02:05:10,572807098-03:00.png

Keybindings

Keybinding Effect Mnemonic
$mod + t Enter task mode T is for task
$mod + t + n Create a new task N is for new
$mod + t + v Create a new task pre-filled with the clipboard contents V as in Ctrl+V
$mod + t + d Mark a task as Done D is for done
$mod + t + s Sync tasks with server running task sync S is for sync
$mod + t + a Annotate a task with multi-line text A is for annotate
$mod + t + i Get full task information by running task info I is for info
$mod + t + r Remove a task R is for remove

Sway Config

## Task
set $task_create exec ~/.config/wofi/scripts/task_create.py
set $task_create_from_clipboard exec ~/.config/wofi/scripts/task_create.py clipboard
set $task_done exec ~/.config/wofi/scripts/task_done.py
set $task_annotate exec ~/.config/wofi/scripts/task_annotate.py
set $task_info exec ~/.config/wofi/scripts/task_info.py
set $task_remove exec ~/.config/wofi/scripts/task_remove.py
set $task_sync exec notify-send "Task Sync" "\n$(task sync 2>&1)"

set $pomodoro_toggle exec ~/.config/waybar/modules/modules/pomodoro/pomodoro.sh toggle
set $pomodoro_end exec ~/.config/waybar/modules/modules/pomodoro/pomodoro.sh end

## task
mode "task" {
  bindsym --to-code --no-repeat {
    n $task_create; $dismiss; $default
    v $task_create_from_clipboard; $dismiss; $default
    d $task_done; $dismiss; $default
    s $task_sync; $dismiss; $default
    a $task_annotate; $dismiss; $default
    i $task_info; $dismiss; $default
    r $task_remove; $dismiss; $default
    p $pomodoro_toggle; $dismiss; $default
    b $pomodoro_end; $dismiss; $default

    # back to normal: Enter or Escape
    Return $dismiss; $default
    Escape $dismiss; $default
  }
}
bindsym $mod+t $mode_notify task; mode "task"

Enter fullscreen mode Exit fullscreen mode

Waybar Config

    "custom/pomodoro": {
        "format": "{}",
        "tooltip": true,
        "exec": "cat $HOME/.cache/waybar/output/pomodoro",
        "return-type": "json",
        "interval": 1,
        "on-click": "$HOME/.config/waybar/modules/modules/pomodoro/pomodoro.py toggle",
        "on-click-right": "$HOME/.config/waybar/modules/modules/pomodoro/pomodoro.py end",
        "on-click-middle": "$HOME/.config/waybar/modules/modules/pomodoro/pomodoro.py lock",
        "on-scroll-up": "$HOME/.config/waybar/modules/modules/pomodoro/pomodoro.py time +60",
        "on-scroll-down": "$HOME/.config/waybar/modules/modules/pomodoro/pomodoro.py time -60"
    },
    "custom/task": {
        "format": "{}",
        "tooltip": true,
        "interval": 2,
        "exec": "cat $HOME/.cache/waybar/output/task",
        "return-type": "json"
    },

Enter fullscreen mode Exit fullscreen mode

The Scripts

task_create.py

This script is responsible for opening a Wofi dialog and creating a new task.

#!/usr/bin/env python

import sys
import json
import subprocess

paste = False

if len(sys.argv) == 2 and sys.argv[1] == 'clipboard':
    paste = True

tasks = json.loads(subprocess.check_output(['task', 'export', 'status:pending']))

projects = {x['project'] for x in tasks if 'project' in x}
projects = sorted(list(projects))

projects.insert(0, 'none')

wofi = subprocess.Popen(['wofi', '-d', '-p', 'Select Project', '-k', '/dev/null'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proj = wofi.communicate('\n'.join(projects).encode())[0].decode()
proj = proj.strip()

if len(proj) > 0:
    content = None
    if paste:
        try:
            content = subprocess.check_output(['wl-paste', '-n', '-p']).decode()
        except:
            pass

    if content:
        desc = subprocess.check_output(['wofi', '-d', '--lines', '1', '-p', 'Task Description', '--search', content])
    else:
        desc = subprocess.check_output(['wofi', '-d', '--lines', '1', '-p', 'Task Description'])

    desc = desc.strip()

    if len(desc) > 0:
        print(subprocess.check_output(['task', 'add', 'project:{}'.format(proj), desc]))

Enter fullscreen mode Exit fullscreen mode

task_info.py

Displays full information about the select task.

#!/usr/bin/env python

import os
import json
import tempfile
import subprocess

tasks = json.loads(subprocess.check_output(['task', 'export', 'status:pending']))

sorted_tasks = sorted(tasks, key=lambda x: x['id'])

task_string = []

for t in sorted_tasks:
    id = str(t['id']).zfill(3)

    project = '{}'.format(t.get('project', 'none').strip())
    description = t['description'].strip()

    s = '<b>[{}]</b> - <b>{}</b>\n{}'.format(id, project, description)
    task_string.append(s)

wofi = subprocess.Popen(['wofi', '-d', '-p', 'Select Task to Describe', '-k', '/dev/null', '-Ddmenu-separator=|'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = wofi.communicate('|'.join(task_string).encode())[0].decode()

if len(out) > 0:
    tid = int(out[4:7])

    fd, filename = tempfile.mkstemp()

    try:
        info = subprocess.check_output(['task', str(tid), 'info'])

        with os.fdopen(fd, 'w') as f:
            f.write(info.decode())

        subprocess.check_output(['kitty', 'vim', filename])
    except Exception as e:
        print(str(e), flush=True)
    finally:
        os.remove(filename)

Enter fullscreen mode Exit fullscreen mode

task_annotate.py

This script annotates a task with multi-line text using vim.

#!/usr/bin/env python

import os
import json
import tempfile
import subprocess

tasks = json.loads(subprocess.check_output(['task', 'export', 'status:pending']))

sorted_tasks = sorted(tasks, key=lambda x: x['id'])

task_string = []

for t in sorted_tasks:
    id = str(t['id']).zfill(3)

    project = '{}'.format(t.get('project', 'none').strip())
    description = t['description'].strip()

    s = '<b>[{}]</b> - <b>{}</b>\n{}'.format(id, project, description)
    task_string.append(s)

wofi = subprocess.Popen(['wofi', '-d', '-p', 'Select Task to Annotate', '-k', '/dev/null', '-Ddmenu-separator=|'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = wofi.communicate('|'.join(task_string).encode())[0].decode()

if len(out) > 0:
    tid = int(out[4:7])

    fd, filename = tempfile.mkstemp()

    try:
        subprocess.check_output(['kitty', 'vim', filename])

        with os.fdopen(fd, 'r') as f:
            data = f.read()
            if len(data) > 0:
                print(subprocess.check_output(['task', 'annotate', str(tid), data]))
    except Exception as e:
        print(str(e), flush=True)
    finally:
        os.remove(filename)

Enter fullscreen mode Exit fullscreen mode

task_done.py

This script marks a task as done.

#!/usr/bin/env python

import json
import subprocess

tasks = json.loads(subprocess.check_output(['task', 'export', 'status:pending']))

sorted_tasks = sorted(tasks, key=lambda x: x['id'])

task_string = []

for t in sorted_tasks:
    id = str(t['id']).zfill(3)

    project = '{}'.format(t.get('project', 'none').strip())
    description = t['description'].strip()

    s = '<b>[{}]</b> - <b>{}</b>\n{}'.format(id, project, description)
    task_string.append(s)

wofi = subprocess.Popen(['wofi', '-d', '-p', 'Select Task to Complete', '-k', '/dev/null', '-Ddmenu-separator=|'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = wofi.communicate('|'.join(task_string).encode())[0].decode()

if len(out) > 0:
    tid = int(out[4:7])

    print(subprocess.check_output(['task', 'done', str(tid)])[0].decode())

Enter fullscreen mode Exit fullscreen mode

task_remove.py

This script removes a task.

#!/usr/bin/env python

import json
import subprocess

tasks = json.loads(subprocess.check_output(['task', 'export', 'status:pending']))

sorted_tasks = sorted(tasks, key=lambda x: x['id'])

task_string = []

for t in sorted_tasks:
    id = str(t['id']).zfill(3)

    project = '{}'.format(t.get('project', 'none').strip())
    description = t['description'].strip()

    s = '<b>[{}]</b> - <b>{}</b>\n{}'.format(id, project, description)
    task_string.append(s)

wofi = subprocess.Popen(['wofi', '-d', '-p', 'Select Task to Delete', '-k', '/dev/null', '-Ddmenu-separator=|'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = wofi.communicate('|'.join(task_string).encode())[0].decode()

if len(out) > 0:
    tid = int(out[4:7])

    wofi = subprocess.Popen(['wofi', '-d', '-p', 'Are you sure you want to remove task {}?'.format(tid), '-k', '/dev/null'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out = wofi.communicate('No\nYes'.encode())[0].decode().strip()

    if out == 'Yes':

        rm = subprocess.Popen(['task', 'rm', str(tid)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out = rm.communicate(input='yes\n'.encode())

        print(out[0].decode())

Enter fullscreen mode Exit fullscreen mode

Discussion (0)