You’ve probably heard of the Pomodoro Technique, where you work in discrete time segments, with equally discrete break intervals. Not that I’m an ageist, but I assumed this technique more useful for people who haven’t had enough time working. However, I do believe we can train our brains, so perhaps making the work/relax cycle more specific and focused may convert the cycle into practice, and condition our brains.
However, while I would like to focus my thoughts in discrete bursts, I don’t want to interrupt the flow. You know, that mental groove where one is really productive. I certainly don’t want a jarring ding to shatter that flow.
I’ve decided to try an experiment to see if I can make the cycle changes noticeable, but not disturbing. What follows is the code I’m using. I’m not supplying it as a package, as it is not general enough. It is also easy enough to explain, that you should just steal my code, and make something more personal. However, if stealing the details is too much, you should take advantage of org-pomodoro, as this seems to be well designed and customizable package.
My goals are:
- Start an unobtrusive, but obvious timer. This requires it to not be visual.
- Stop the timer, but again, noticeable but not mentally interrupting (just in case I’m in the flow).
- Need full control for starting.
- Directly tie a work session with my TODO list in an org-mode formatted file.
Oh, and it needs to be controllable from Emacs. But that is a given.
Here’s my idea, I play/pause my music as the notifications. While this may (will) change in the future, the music player will be Spotify, and this code will currently be Mac-specific. When you steal this code to make your own version, you should be able to swap out the command line calls to VLC or something else.
Setting a Timer
Of course, this Pomodoro-technique is all about the timers. So, let’s create a global variable to hold a reference to the focus timer. Keep in mind, you don’t normally have to store this, but I figured I might want to query it at times.
(defvar ha-focus-timer nil "A timer reference for the ha-focus functions")
Timers, in Emacs, are pretty easy, as one just needs to call the flexible function,
run-at-time, give it some time specification and a function that acts like a callback. I’m going wrap this function to make the interface simpler, and allows me to expand it later.
(defun ha-focus-countdown-timer (minutes fun) (let ((the-future (* minutes 60))) (run-at-time the-future nil fun)))
Controlling the Music
I need to make commands that can control the music, and use that as the indicators of my state. This may also work, like Pavlov’s beagle, to train myself to enter into the flow quicker.
I will assume that I already have work-focus appropriate music loaded and ready to go. If so, the following AppleScript starts the music:
tell application "Spotify" next track delay 1 play end tell
The script calls
next track to begin the session at the beginning of a song.
Perhaps we should wrap this in a Shell Script:
I need another script to stop the music (or something equivalent) when the focused time is over.
When a break period completes, I don’t want to automatically start the next session. I want to start it deliberately. However, I do want to know when the five minute break is over. I’m okay with this being more noticeable.
tell application "Spotify" to pause delay 300 -- Wait 5 minutes set v to output volume of (get volume settings) set volume output volume 1 -- Not quite a whisper say "Break time over. Back on your head." set volume output volume v
And again, I may want a wrapper shell script:
While this isn’t for everyone, I do want to tie my work session with an Org file heading, so I want to clock in (as that also makes my text capturing easier). So my
ha-focus-begin function needs to:
- starts a timer
- starts the music
- clocks in
This function is the primary interface and probably the only function I need to call:
(defun ha-focus-begin () "Start a concerted, focused effort, ala Pomodoro Technique. We first clock into the current org-mode header (or last one), start some music to indicate we are working, and set a timer. Call `ha-focus-break' when finished." (interactive) (ha-focus-countdown-timer 25 'ha-focus-break) (ha-focus--command "tell application \"Spotify\" to play") (if (eq major-mode 'org-mode) (org-clock-in) (org-clock-in-last)))
When the focused time is over, we have a callback,
ha-focus-break, which stops the music, clocks out, and starts a new timer for the break period:
(defun ha-focus-break () "Stop the focused time by stopping the music. This also starts another break timer, that calls `ha-focus-break-over' when finished." (interactive) (ha-focus-countdown-timer 5 'ha-focus-break-over) (ha-focus--command "tell application \"Spotify\" to pause") (org-clock-out) (message "Time to take a break."))
Yeah, both the Emacs message as well as the cessation of music is pretty subtle. We’ll see how that goes.
ha-focus-break interactive? Well, I may need to leave, and this stops the music and clocks out.
Finally, we need a timer to remind me when the break is over:
(defun ha-focus-break-over () "Message me to know that the break time is over. Notice that this doesn't start anything automatically, as I may have simply wandered off." (ha-focus--command "set v to output volume of (get volume settings) set volume output volume 1 say \"Break time over. Back on your head.\" set volume output volume v"))
That is all we need. You will notice that my Emacs functions are self-contained, and don’t call external files. That may change.
Asynchronous Shell Commands
The output from the shell commands shouldn’t return anything, so we don’t need to display their output. If I use
shell-command, the output is too small, and Emacs uses the mini-buffer. Since I don’t want to wait on the AppleScripts, we need to use
async-shell-command, however, this always displays a buffer of the output.
ha-focus--command wrapper function is a little more complicated (but not much) since it uses
async-start-process. To make future troubleshooting possible, I will give it a callback function.
(defun ha-focus--command (osascript) "Runs OSASCRIPT by passing to the `osascript' command asynchronously." (async-start-process "focus-os" "osascript" 'ha-focus--command-callback "-e" osascript))
Currently, my callback function doesn’t need to do anything, so I ignore the
proc process object given. However, you can see how I might want to display a bit more information when troubleshooting.
(defun ha-focus--command-callback (proc) "Asynchronously called when the `osascript' process finishes." (message "Finished calling osascript."))
Minimal viable product.
I can’t imagine much more than I need that a simple keybinding to start a session.
(global-set-key (kbd "<f15>") 'ha-focus-begin) (global-set-key (kbd "S-<f15>") 'ha-focus-break)
I just wrote this code one evening, so I’ll let you know if it helps me.