Introducing Headspace

hammerspoon logo

I use Hammerspoon to enhance my OSX with custom UI and automations, while also creating "monotasking" deep work environments to keep me focused.

I am very excited about this post... I'm pretty sure that I've been working on it alongside the name-sake tool for a straight month.

Headspace has been the title of this series, and it's what I call the last automation in this series. Let's jump straight into it with a demo.

If you want to share this video with someone, it's on youtube.

Here's what Headspace offers:

  • Fully configurable for you and your work-style.
  • Define workspaces for your important work:
    • Launch relevant applications and quit distracting applications based on tags.
    • Block distracting hyper.lua shortcuts to protect focus.
  • Toggl.com tracking presets per space… or a custom input at any time.

I've been using Headspace daily for about a month. I love it. I am sure that you could create something very similar using a variety of automation tools. Build your own version or clone mine!

How Headspace Came to Be

After writing the "split window" function, I realized there were window layouts I preferred for specific tasks… starting with my daily review in Things 3, but also my daily standup.

To have an idea of the complexity of one of these tasks: Every work weekday at 4:30p in order to perform my Daily Review…1

  1. I close all distracting apps by running "quitall" in Alfred.
  2. I start a timer using Toggl.app, I choose "Planning" as the project and type in "Review" as the description.
  3. I launch Things and Drafts using hyper.lua hotkeys.
  4. I setup the first Things window:
    • Move to right 30% (HYPER+M,T)
    • Hide its sidebar (CMD+/)
    • Go to Today (CMD+F, tod, <return>).
  5. I open a second Things window (CMD+OPT+N).
  6. I setup the work Things window:
    • Move to left 70% (HYPER+M,R)
    • Show its sidebar (CMD+/)
    • Go to Anytime (CMD+F, any, <return>).
  7. I then follow the checklist in the right window to do the work in the left window.
screenshot of a sample daily review

This is how I like to conduct my daily review... I have the To-do with a checklist for the ritual itself on the right, and I work in the pane to the left as I work through the checklist.

That's a lot of steps! Every day! 😰

In early May, I started to write a simple "layout chooser". I started with a hs.chooser, and just had it wired to functions that moved around windows and set up the windows in my workspace the way I wanted it. I realized that since I'm writing a function for a specific task, then I know the time tracking data for that task. I could automate my time tracking as well! I wrote the first curl-based version of my toggl.lua script and threw a toggl.start() call in the setup function.

Within a couple minutes or hacking, I had a button than when pressed could rearrange my computer into one of two "scenes," which immediately set up my workspace and started the correct toggl timer. I pressed one key, chose my task, and started to work.

game changer gif

I realized that choosing a space was a statement of intentionality. Headspace had become a tool that cleared the space for me to focus on my intent, and automatically removed obstacles.

I had been really interested in the idea or having different spaces for productivity, since I had seen Spaceship You and heard CGP Grey's additional commentary on the topic in Cortex Ep. 102.

As I already was copying Grey's use of toggl to track my whole work day, I realized I could build a tool of spatial intentionality. Removing distraction, staying focused, and tracking how much time I spend moving the needle.

I started adding entries for time tracking every major modality of work that I do daily, even if it didn't require a special window layout. I already had been using projects in Toggl for this, so I just copied them into Headspace. I used it for a week with just the major modalities I had created, occasionally flipping over to the standard toggl.app to enter a custom description for a timer.

Blocking Hyper Distractions

Notification informing user that Slack has been blocked

The ability to open any application with a single keypress is awesomely… distracting.

I wrote a modification to hyper.lua that blocked applications had had declared should never be in this space. You can still manually go launch them, but that little nudge helps me stay on task and not open Slack just to tell a friend a cool thing I did in Hammerspoon. 😛

hyper.allowed = function(app)
  if hyper.blocking_enabled then
    if app.tags then
      if hs.settings.get("only") then
        return hs.fnutils.some(hs.settings.get("only"), function(tag)
          return hs.fnutils.contains(app.tags, tag)
        end)
      else
        if hs.settings.get("never") then
          return hs.fnutils.every(hs.settings.get("never"), function(tag)
            return not hs.fnutils.contains(app.tags, tag)
          end)
        end
      end
    end
  end
  return true
end

hyper.launch = function(app)
  if hyper.allowed(app) then
    hs.application.launchOrFocusByBundleID(app.bundleID)
  else
    hs.notify.show("Blocked " .. app.bundleID, "You have to switch headspaces", "")
  end
end

(from hyper.lua)

View Current Timer

showing the current timer

I overloaded the placeholder text for the hs.chooser so that if there's a currently running timer, I can see what my current intention. This often keeps me on track. Because the "allowed applications" is tied to a space, if I want to freely use Slack or Email, I have to look at my intention in the timer first before I change the headspace. I have found myself starting to stay the course and finish the work. 👌

:showCallback(function()
  local current = toggl.current_timer()
  if current and current.data then
    local timer = current.data
    local str = "🔴 :"
    if timer.description then
      str = str .. " " .. timer.description
    end
    if timer.pid then
      local project = toggl.get_project(timer.pid)
      if project and project.data then
        str = str .. " ("  .. project.data.name .. ")"
      end
    end
    local duration = math.floor((hs.timer.secondsSinceEpoch() + current.data.duration) / 60) .. "m"
    chooser:placeholderText(str .. " :: " .. duration)
  end
end)

(from headspace.lua)

Custom Timers

showing the custom timer

I also rewrote the hs.chooser callback so that anything you type can be started as a toggl timer. I can now use just Headspace for all time tracking, only running toggl.app for the Timeline feature.

:queryChangedCallback(function(searchQuery)
  local query = string.lower(searchQuery)
  local results = fn.filter(module.config.spaces, function(space)
    local text = string.lower(space.text)
    local subText = string.lower(space.subText)
    return (string.match(text, query) or string.match(subText, query))
  end)

  table.insert(results, {
  })

  chooser:choices(results)
end)

(from headspace.lua)

Not bouncing back and forth between headspaces.lua for presets and Toggl.app for custom means that my time-tracking has become more consistent and painless.

It was really fun to write this. I was working on this while using it… so I was UX lead, developer, and customer all in one. My passion is making tools, and this one has been very satisfying to create.

Daily Usage

So what is it like to actually use?

Full disclosure... this is largely solving a problem of my own making. Automations like the Hyper key, combined with a decade of not valuing the practice of sustained intentionality2 means that it's easy for my monkey brain to run away and be distracted.

That being said, I do not think I'm alone in this. Several thinkers in the space (Shawn Blanc3, Mike Hurley and CGP Grey, David Sparks and Mike Schmidt, Rosemary Orchard, etc.) have all talked about how they love using their iPad as a tool of focus, because iPad OS is a "single-tasking" OS. In some ways, I'm attempting to emulate that on a multi-tasking Macbook Pro, but on my terms. I can turn it on and off.

Blocking my custom hyper-key shortcuts was incredibly frustrating for the first week or so. I occasionally got physically angry at my computer. "Why won't you do what I tell you!!!! oh. yeah. I said I was focusing."

I have found that this effort is slowly re-wiring my brain into putting those distracting urgent messages and "just-checks"4 into Things.app tasks and Drafts.app. I may even attempt to automate this, where if I try to access an app that is blocked, just open a Drafts.app draft with some context about the time, context, and the app I had just tried to access.

My time-tracking is much more consistent, and honest. At the moment, I don't have a "stop" timer setup until the shutdown routine space that I run at the end of my day. That means I'm always running a timer. My fallback timer is Communication. It's accurate enough for now: I enter a "focused space" like Writing or Research and distractions and communication applications are blocked. When I'm done, I leave the focused space (stopping that timer) and I'm in Communication which everything is allowed, and I'm checking in with my team... it's pretty accurate to what I do now.

I think in the future I'd like to be more intentional, spend more time in focus-type blocks and less time in communication/distraction. But I have the flexibility to redesign headspace to nudge me in the right direction.

Here's a simple "space" definition in "deep.lua" that I show in the demo video.

table.insert(config.spaces, {
  text = "Deep",
  subText = "Work deeply on focused work (Deep)",
  image = hs.image.imageFromAppBundle('com.culturedcode.ThingsMac'),
  toggl_proj = config.projects.deep,
  never = {'#distraction'},
  setup = 'deep'
})

config.setup.deep = function()
  hs.urlevent.openURL(
    "things:///show?id=anytime&filter=@ProctorU,$High"
  )
end

For a given space, I build a table for the hs.chooser, passing in information for toggl.com, and tags that I want to block/enable.

If I want to enable more complex actions, I can define a "setup" function. I can do pretty much anything in there.

This tool has me re-thinking my computer usage. Pretty much anytime I find myself doing the same thing over and over I stop and encode it into my system. I keep hiding the UI of Drafts.app for minimalistic writing? Encode that into Write. Always open the same windows in the same places each time I run standup? Encode that into Standup.

It may not save me all that much time, but I have to imagine that every small piece of friction removed is hopefully making the way for useful work. It certainly gives me joy... every time I punch in my standup and it sets itself up for me so that all I do is hit "Join Call?" I grin like a fool every time.

Where is this headed?

Well, I can't stop tinkering. It's been changing as I write this blog post. 🤷

I am still tweaking my toggl project to headspace relationship as I begin to understand my own work and behavior, I am trying to track and encourage spending my time in meaningful ways.

I'll probably attempt to write a tool to set automatic timers/limits on how much time I spend in certain spaces. I spent way too much time just in Communication mode, and I like the idea of having measured work blocks where I can trust my system to give me breaks at intervals.

I think this series is basically done, but it has led me down some neat automation rabbit holes that I'll probably feature in standalone posts. For now, you can peek into my spaces folder and see how I'm classifying my work. Maybe you'll get some ideas for your own automations.

I don't really expect anyone else to try this... but if you do: please shoot me an email or create a github issue. If there's enough interest, I'll submodule out headspace to be it's own Spoon or something.

If you really want to try Headspace and doesn't want to code up some lua, I'll either help you, or try to hack together a control panel using hs.dialog so that it is self-configurable with a UI.


  1. I guess this is a bit of meta for my own process: 

    • In order to do information work:
      • Check the calendar. What was my original plan for this time?
      • Select task.
      • Start a timer, stating intention for this time and recording metrics.
      • Close anything unrelated to preserve my focus.
        • Apps I usually don't care about.
        • Browser tabs go into Instapaper.
      • Open any applications or windows required by the work.
      • EITHER:
        • set up a single work window full screen (usually)
        • set up a consistent layout of windows for the task (occasionally)
      • Work for the allotted time block.
      • Attempt to resist urgent, immediate distractions to accomplish my task.
  2. This is my current favorite definition of Focus, taken from Mike Schmidtz on Focused Episode 100

  3. As for the type of work that my iPad is superior? Writing. iOS is, by nature, a distraction-free work environment. I love that I have to move a little bit slower within iOS, and that apps are almost always in full screen mode. This is a feature to me. 

    (First Impressions of the New 12.9″ iPad Pro for Writing and Photography)

  4. Alternatives to the Just Checks - shawnblanc.net 


Changelog
  • 2024-02-20 09:38:14 -0600
    Convert to permalinks

  • 2022-06-08 11:31:29 -0500
    Rename articles

  • 2020-09-28 09:20:43 -0500
    Change repo name

    I decided to change the name of my hammerspoon configuration repo to
    `hammerspoon-config` to avoid confusion... it's not the actual
    hammerspoon executable, it's a configuration for that executable.

  • 2020-06-19 09:10:38 -0500
    Adjust publishing time to CDT

    * head desk *

  • 2020-06-19 08:26:28 -0500
    Adjust publish time

  • 2020-06-18 14:28:11 -0500
    Set published date

  • 2020-06-18 14:26:02 -0500
    Move everything to CST

    Don't know why I didn't do that before. It caused _no_ end of
    problems.

  • 2020-06-18 08:27:19 -0500
    Update video, fix typos

  • 2020-06-17 22:15:16 -0500
    Second draft... adding images and video

  • 2020-06-16 14:22:25 -0500
    First draft of Hammerspoon Headspace