;;; org-clock-waybar.el --- Summary ;; Copyright (C) 2021 Gergely Polonkai ;; Author: Gergely Polonkai ;; Keywords: org, clocking, waybar ;; Version: 1.0 ;; Package-Requires: ((emacs "26.1")) ;; URL: https://gitea.polonkai.eu/gergely/org-clock-waybar ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Export the currently clocked-in task in JSON format that Waybar can process ;; ;; To use it, customize the `org-clock-waybar-filename' variable (defaults to ;; $XDG_CONFIG_HOME/waybar-current-task.json) and add the following snippet to ;; your Waybar config: ;; ;; "custom/org": { ;; "format": " {}", ;; "return-type": "json", ;; "restart-interval": 5, ;; "exec": "cat /home/yourusername/.cache/waybar-current-task.json" ;; } ;;; Code: (require 'xdg) (require 'json) (require 'org-clock) (defgroup org-clock-waybar nil "Send current clocked task to a JSON file for Waybar visualization" :group 'emacs) (defcustom org-clock-waybar-filename (expand-file-name "waybar-current-task.json" (xdg-cache-home)) "Name of the file to save task data to." :type 'string :group 'org-clock-waybar) (defcustom org-clock-waybar-not-clocked-in-text "Not clocked in" "Text to display when not clocked in on any task." :type 'string :group 'org-clock-waybar) (defcustom org-clock-waybar-text-function nil "Function to generate the title text. The function must return a single string. When nil, `org-clock-waybar-get-task-title' is used." :type 'function :group 'org-clock-waybar) (defcustom org-clock-waybar-alt-function nil "Function to generate the alternative text. The function must return a single string. When nil, `org-clock-waybar-get-task-category' is used." :type 'function :group 'org-clock-waybar) (defcustom org-clock-waybar-class-function nil "Function to generate the class. The function must either return a string, or a list of strings. When nil, `org-clock-waybar-get-tags' is used." :type 'function :group 'org-clock-waybar) (defcustom org-clock-waybar-tooltip-function nil "Function to generate the tooltip. The function must return a string. When nil, `org-clock-waybar-get-tooltip' is used." :type 'function :group 'org-clock-waybar) (defcustom org-clock-waybar-percentage-function nil "Function to generate the percentage text. When nil, the percentage text will be an empty string." :type 'function :group 'org-clock-waybar) (defconst org-clock-waybar-filename-coding-system (if (coding-system-p 'utf-8-emacs) 'utf-8-emacs 'emacs-mule) "Coding system of the file `org-clock-waybar-filename'.") (defsubst org-clock-waybar-get-task-title () "Get the title of TASK." (if (org-clocking-p) (substring-no-properties org-clock-current-task) org-clock-waybar-not-clocked-in-text)) (defsubst org-clock-waybar-get-task-category () "Get the category of TASK." (when (org-clocking-p) (get-text-property 0 'org-category org-clock-current-task))) (defun org-clock-waybar--list-of-strings-p (object) "Return t if OBJECT is a list of strings." (not (null (delq nil (mapcar (lambda (x) (and (stringp x) x)) object))))) (defun org-clock-waybar-get-tooltip () "The default tooltip to send to waybar." (when (org-clocking-p) (let ((clocked-time (org-clock-get-clocked-time))) (format "%s: %s (%s)" (org-clock-waybar-get-task-category) (org-clock-waybar-get-task-title) (org-duration-from-minutes clocked-time))))) (defun org-clock-waybar-get-tags () "Get the tags of the currently clocked-in task." (when (org-clocking-p) (save-window-excursion (org-clock-goto) (org-get-tags)))) (defun org-clock-waybar--get-clocked-task-json () "Get the currently clocked-in task’s data as a stringified JSON object. The output is in JSON format constructed in a way so Waybar can process it. If there is no clocked in task, alt becomes empty and text will be set to the value of `org-clock-waybar-not-clocked-in-text'." (let* ((text-func (or 'org-clock-waybar-text-function 'org-clock-waybar-get-task-title)) (text (funcall text-func)) (alt-func (or 'org-clock-waybar-alt-function 'org-clock-waybar-get-task-category)) (alt (funcall alt-func)) (tooltip-func (or 'org-clock-waybar-tooltip-function 'org-clock-waybar-get-tooltip)) (tooltip (funcall tooltip-func)) (class-func (or 'org-clock-waybar-class-function 'org-clock-waybar-get-tags)) (class (funcall class-func)) (percentage (when (fboundp 'org-clock-waybar-percentage-function) (funcall 'org-clock-waybar-percentage-function))) (output (json-new-object))) (or (null title) (stringp title) (error "Title must be a string (org-clock-waybar)!")) (or (null alt) (stringp alt) (error "Alt text must be a string (org-clock-waybar)!")) (or (null tooltip) (stringp tooltip) (error "Tooltip must be a string (org-clock-waybar)!")) (or (null class) (stringp class) (org-clock-waybar--list-of-strings-p class) (error "Class must be a string or a list of strings (org-clock-waybar)!")) (or (null percentage) (stringp percentage) (error "Percentage must be a string (org-clock-waybar)!")) (setq output (json-add-to-object output "text" (or text ""))) (setq output (json-add-to-object output "alt" (or alt ""))) (setq output (json-add-to-object output "tooltip" (or tooltip ""))) (setq output (json-add-to-object output "class" (or class ""))) (setq output (json-add-to-object output "percentage" (or percentage ""))) (json-encode output))) (defun org-clock-waybar-save-task () "Save the current clocked in task to `org-clock-waybar-filename'." (with-temp-buffer (erase-buffer) (set-buffer-file-coding-system org-clock-waybar-filename-coding-system) (insert (org-clock-waybar--get-clocked-task-json)) (write-file org-clock-waybar-filename))) (defun org-clock-waybar-ouptut-task () "Output the current task in JSON format Waybar can understand. This function is ought to be used via Emacsclient: emacsclient --eval '(org-clock-waybar-output-task)'" (org-clock-waybar--get-clocked-task-json)) (defun org-clock-waybar-setup () "Setup org-clock-waybar. It adds `org-clock-waybar-save-task' to both `org-clock-in-hook' and `org-clock-out-hook'." (add-hook 'org-clock-in-hook #'org-clock-waybar-save-task) (add-hook 'org-clock-out-hook #'org-clock-waybar-save-task) (add-hook 'kill-emacs-hook #'org-clock-waybar-save-task)) (provide 'org-clock-waybar) ;;; org-clock-waybar.el ends here