I’m off mid morning today for my son’s end of term church service - so rather than getting stuck into some serious coding I thought I’d have a play with Hammerspoon to bring the active Org mode clock to the macOS menu bar.

Screenshot 2019-12-13 at 10.34.03.png

Hammerspoon is a macOS automation scripting tool which uses the language Lua . I’ve dabbled with Lua in the past, even going so far as to make it a scripting language in my app Trunk Notes (now discontinued so don’t go looking for it on the App Store.)

I’ve recently decided to stop using Harvest for tracking time and start using the ’ clocking ’ features of Org mode . As well as saving $12 a month I’m keeping my records in plain text and can easily run scripts to get the kind of stats I’m interested in on my performance on a per client and per project basis.

One issue with this has been I have to use Emacs to see if I’ve got a timer running. This might sound odd for those of you who live in Emacs - but I have to spend quite a lot of time in Xcode and Android Studio.

This is a solved problem (for example https://github.com/jordonbiondo/osx-org-clock-menubar), however I wanted to put something together myself and see if over time I could improve on previous attempts.

Rather than writing any Emacs Lisp to accomplish this I’m using emacsclient and a couple of Org mode functions. The first is org-clock-is-active - which will return nil if there isn’t currently a clock running. The second is org-clock-get-clock-string. This returns something used by Emacs and includes face information - however it’s quite easy to extract what I need.

Once Hammerspoon is installed and running it is relatively simple to write/install a script to perform all kinds of fun automation with macOS.

To keep things nice I’ve created a Lua script called clocking.lua which I can then import from init.lua (that main script that Hammerspoon runs on start up).

Creating a menubar item using Hammerspoon is dead simple:

local clockingMenu = hs.menubar.new()
clockingMenu:setTitle("No Task")

An impressively minimal API for this, and most other tasks - exactly what you want from an automation framework.

Once I’d got a menubar working then the next item to tick off was to get a result from running emacsclient. Since I intend over time to expand on this initial prototype I created a function to easily run Emacs Lisp:

local function eval(sexp, callback)
   hs.task.new(
      "/usr/local/bin/emacsclient",
      function(exitCode, stdOut, stdErr)
         if exitCode == 0 then
            callback(trim(stdOut))
         end
      end,
      { "--eval", sexp }
   ):start()
end

A small utility function I grabbed from http://lua-users.org/wiki/StringTrim helped to clean up the output for easier processing later on.

Next I needed to determine if a clock was running, and if it was put something useful in the menu bar:

local function updateClockingMenu()
   eval(
      "(org-clock-is-active)",
      function(value)
         if value == "nil" then
            clockingMenu:setTitle("No Task")
         else
            eval(
                "(org-clock-get-clock-string)",
                function(value)
                   clockingMenu:setTitle(string.match(value, '"(.+)"'))
                end
            )
         end
      end
   )
end

Finally I need to get my menu item to update periodically. For this I can use the API method hs.timer.doEvery:

local function startUpdatingClockingMenu()
   hs.timer.doEvery(10, updateClockingMenu)
end

I’m not yet ready to create a spoon (a Hammerspoon module containing some specific functionality) but I have created a GitHub Gist containing the code you’ll need if you’d like to get this working yourself:

https://gist.github.com/mgkennard/e60ea3b354963293035a315d4b9d69f0

You might need to change the path to your emacsclient executable

Just pop the file clocking.lua in your Hammerspoon config direction and then add the following to your init.lua:

local clocking = require "clocking"

clocking.init()

The next step will be to start and stop the clock from the menubar, and potentially show a list of recent clocks and choose which one to use.