Org Clock in i3blocks
Lately I have been making use of org-clock to keep track of my time spent on various tasks while freelancing. For billing purposes and cost estimation it has been very valuable.
* Clock Report #+BEGIN: clocktable :scope file :maxlevel 4 #+CAPTION: Clock summary at 2024-01-31 Wed 22:20 | Headline | Time | | | |------------------------+--------+------+------| | Total time | 0:40 | | | |------------------------+--------+------+------| | Clock Report | 0:40 | | | | \_ Work Sessions | | 0:40 | | | \_ Example Task | | | 0:40 | #+END: ** Website * Example Task :LOGBOOK: CLOCK: 2024-01-30 Tue 12:52 2024-01-30 Tue 13:32 => 0:40 :END: - Shipping information - Processing time
When clocked in, I need to easily see it, so that I do not forget to clock out.
Figure 1: Example
1. New Solution
My new solution is a bit lighter on the CPU. Specifically, it now writes the org modeline to a file so we can easily read it for display in i3blocks. Here is the Emacs configuration:
(defun write-mode-line-to-file () "Write the current mode line string to /tmp/modeline." (with-temp-file "/tmp/org-clock" (insert (format "%s" (if (boundp 'org-mode-line-string) org-mode-line-string ""))))) (defun delete-mode-line-file () "Delete the /tmp/modeline file." (when (file-exists-p "/tmp/org-clock") (delete-file "/tmp/org-clock"))) (defvar custom/org-clock-update-timer nil "Timer for updating the mode line file while clocked in.") (defun custom/org-clock-update-mode-line () "Update the mode line file with the current mode line string." (when (and (boundp 'org-clock-current-task) org-clock-current-task) (write-mode-line-to-file))) (defun custom/org-clock-in-advice (orig-fun &rest args) "Advice to write mode line to file on org-clock-in and start timer." (apply orig-fun args) (write-mode-line-to-file) (when custom/org-clock-update-timer (cancel-timer custom/org-clock-update-timer) (setq custom/org-clock-update-timer nil)) (setq custom/org-clock-update-timer (run-at-time "1 min" 60 #'custom/org-clock-update-mode-line))) (defun custom/org-clock-out-advice (orig-fun &rest args) "Advice to delete mode line file on org-clock-out and stop timer." (delete-mode-line-file) (when custom/org-clock-update-timer (cancel-timer custom/org-clock-update-timer) (setq custom/org-clock-update-timer nil)) (apply orig-fun args)) (defun custom/org-clock-cancel-advice (orig-fun &rest args) "Advice to delete mode line file on org-clock-cancel and stop timer." (delete-mode-line-file) (when custom/org-clock-update-timer (cancel-timer custom/org-clock-update-timer) (setq custom/org-clock-update-timer nil)) (apply orig-fun args)) (advice-add 'org-clock-in :around #'custom/org-clock-in-advice) (advice-add 'org-clock-out :around #'custom/org-clock-out-advice) (advice-add 'org-clock-cancel :around #'custom/org-clock-cancel-advice)
1.1. Adding it to i3blocks
You can add a section in the configuration file to call the script.
[org_clock] command=test -f /tmp/org-clock && echo "⏰" $(cat /tmp/org-clock | sed 's/<//g; s/>//g; s/(//g; s/)//g; s/\[//g; s/\]//g') interval=1 border=#d12755 border_top=1 border_right=0 border_bottom=0 border_left=0
2. Old Solution
2.1. Getting the org-clock information
I have written a small Python script that extracts the necessary information out of a running Emacs server.
#!/usr/bin/env python3 import time import sys import threading from subprocess import run def watchdog(): def f(): time.sleep(2) sys.exit(0) threading.Thread(target=f, daemon=True).start() def main(): # get current task current_task = run([ "emacsclient", "--eval", "(if (boundp 'org-clock-current-task) org-clock-current-task \"\")", ], capture_output=True, shell=False, text=True).stdout # check if not clocked in if not current_task or current_task == "nil\n": return # get mode line string mode_line = run([ "emacsclient", "--eval", "(if (boundp 'org-mode-line-string) org-mode-line-string \"\")", ], capture_output=True, shell=False, text=True).stdout # check if output invalid if not mode_line or not mode_line.startswith('#("'): return # success print("⏰ ", end="") print(mode_line.split('"')[1] .replace("<", "") .replace(">", "") .replace("(", "") .replace(")", "") .replace("[", "") .replace("] ", " - ") .strip()) if __name__ == "__main__": watchdog() try: main() except: pass sys.exit(0)
This outputs the following text, or nothing when not clocked in.
$ chmod +x ./org-clock
$ ./org-clock
⏰ 0:01 - Example Task
2.2. Adding it to i3blocks
You can add a section in the configuration file to call the script.
[org_clock] command=~/.config/i3blocks/org-clock interval=2 border=#d12755 border_top=1 border_right=0 border_bottom=0 border_left=0