
Headspace: Block All The Things!

I use Hammerspoon to enhance my OSX with custom UI and automations, while also creating "monotasking" deep work environments to keep me focused.
JG wrote:
As far as I know, Headspace blocks the opening of applications via the hyper key, right? I was wondering if there is a way to block the opening of applications at all?
Pretty much as soon as I had finished the series on Hammerspoon, I knew I wanted to take the Headspace project further. At that point, it would block opening or switching to an application via the Hyper shortcuts I had defined earlier, but if you opened an application via Finder, Spotlight, or another launcher like Alfred⦠I could get around my little system.
My fingers started building the new neural pathways to get around my own boundaries immediately. So it's time to⦠BLOCK ALL THE THINGS! * ahem *
I had actually taken a couple stabs at the universal blocking before I finished the blog post. I had a pretty good idea that I'd be using the hs.application.watcher
to track what applications had launched, but I could not get it to work.
After a lot of back and forth and some debugging, I eventually realized that I was tripping over Lua's garbage collection. I had instantiated the watcher, but in the local context of a lua module1. This meant that after the function had been called first time, the watcher's object was garbage collected and destroyed.2 I eventually "hoisted" the watcher out to the global namespace like this:
local module = {}
-- ...
if module.watcher_enabled then
module.watcher = hs.application.watcher.new(function(app_name, event, hsapp)
if event == hs.application.watcher.launched then
local app_config = module.config.applications[hsapp:bundleID()]
if not allowed(app_config) then
hs.notify.show(
"Blocked " .. hsapp:name(),
"Current headspace: " .. hs.settings.get("headspace").text,
""
)
hsapp:kill()
end
end
end):start()
end
-- ...
return module
Another advantage to this migration is that now headspace.lua
is not dependent at all on hyper.lua
. You can trigger the Headspace UI any way you like. I have thought about rewriting it as an official Hammerspoon Spoon, but I am not sure if anyone really wants that.
While I was working on it, I added another feature: you can now define a teardown function for a space. The teardown function fires when the space is exited, and I use it to "undo" things. For instance, when I enter my Communication space, I filter Things.app to the set of to-dos related to communicating. When I leave Communication, I want to see my normal "Today" view, and that's easy enough to implement.
config.funcs.agendaFor = {
setup = function()
hs.urlevent.openURL("things:///show?id=anytime&[email protected],Agenda%20For")
end,
teardown = function()
hs.urlevent.openURL("things:///show?id=today")
end
}
I've been using the new Headspace for the past month or so, and it has been really stable. I may end up choosing to use a non-native notification method eventually, just as I tend to turn on Do Not Disturb mode, which effectively blocks Headspace from telling me what it's doing. As it is, I haven't made any major changes to it in a while, mostly just aesthetic changes to the time tracker and adjusting the blocking lists for various spaces.
If you want to give Headspace a try, you can! Here is a minimum viable init.lua for starting to tinker and build your own setup.
Download the following files to your Hammerspoon directory (usually
~/.hammerspoon
):You'll need to set up your toggl secrets in a
.secrets.json
in that same Hammerspoon folder. Use this as a guide:{ "toggl": { "key": "wouldntyouliketoknow", "projects": { "communications": "<communications id>", "deep": "<deep id>", } } }
Here's a minimimum viable init.lua, together with some commentary on what is going on:
-- load in all secrets local secrets = require('secrets') secrets.start('.secrets.json') -- instantiate the config table that will be passed to the headspace module config = {} -- define some applications to block... yours will be different, here's just a place to start. config.applications = { ['org.mozilla.firefox'] = { bundleID = 'org.mozilla.firefox', }, ['com.tinyspeck.slackmacgap'] = { bundleID = 'com.tinyspeck.slackmacgap', tags = {'communication'} }, ['com.apple.iChat'] = { bundleID = 'com.apple.iChat', tags = {'communication', 'distraction'} }, } -- instantiate tables for headspace spaces -- instantiating them early means that you can use `table.insert` to add -- more later. config.spaces = {} config.funcs = {} -- I store the API id of my toggl projects in a the `.secrets.json` config.projects = hs.settings.get("secrets").toggl.projects -- setup spaces -- I recommend having at _least_ two... one for shallow and one that blocks -- distractions for "deep" work. Take what I have here and make it your -- own. table.insert(config.spaces, { text = "Deep", subText = "Work deeply on focused work", image = hs.image.imageFromAppBundle('com.apple.finder'), toggl_proj = config.projects.deep, blacklist = {'distraction', 'communication'}, funcs = 'deep' }) config.funcs.deep = { setup = function() hs.urlevent.openURL("things:///show?id=anytime&[email protected],$High") end, teardown = function() hs.urlevent.openURL("things:///show?id=today") end } table.insert(config.spaces, { text = "Communication", subText = "Intentionally engage with Slack and Email.", image = hs.image.imageFromAppBundle('com.tinyspeck.slackmacgap'), whitelist = {'communication'}, launch = {'communication'}, toggl_proj = config.projects.communication, funcs = 'agendaFor' }) config.funcs.agendaFor = { setup = function() hs.urlevent.openURL("things:///show?id=anytime&[email protected],Agenda%20For") end, teardown = function() hs.urlevent.openURL("things:///show?id=today") end } -- instantiate headspace local headspace = require('headspace') -- enable the watcher to start the blocking headspace:enable_watcher() -- start the module's setup headspace.start(config) -- bind the Space Choosing UI to a key. -- Here I'm just using `ββ₯+k`. hs.hotkey.bind({'cmd', 'alt'}, 'k', nil, headspace.choose)
That's it! I did some smoke testing on my own system to make sure that this works, but I am (most likely) forgetting something. I realize that this is still really confusing, so if you want to give this a whirl, please reach out and I'll be happy to help you. π
You can always take more inspiration from my ever evolving configuration at my github.
Changelog
-
2022-06-08 11:31:29 -0500Rename articles
-
2020-09-28 09:20:43 -0500Change 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-08-14 15:42:21 -0500Forgot to add tags... duh
-
2020-08-07 15:28:50 -0500Adjust the time
-
2020-08-07 15:27:33 -0500New post: Block all the things!