Reminding myself to stay hydrated with Hammerspoon

I’ve found drinking water while at my desk is one of the best things I can do to feel alert and focused throughout the day.

Unfortunately, I often get wrapped up in whatever I’m working on and routinely forget to stop and take that sip. Hours can go by before I realize I haven’t had anything to drink. Especially outside of the summer months when the air is just as dry but no where near hot.

My usual approach is to have some timer app (sometimes my phone, sometimes Timey 3) going on repeat. And while not a terrible approach, but it’s always felt somewhat clunky.

I got to playing around with Hammerspoon the other day and realized with the utilities it provides, I could build a simple “app” reminding me to hydrate with an alert window that I can toggle on/off via the menubar.

After some trial and error (Lua is still something of a mystery to me), here’s what I came up with:

Water = {}

Water.inactiveIcon = [[ASCII:
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	1 . . . . . . . . . . . . . . 3
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . 1 2 . . . . . . 2 3 . . .
]]

Water.activeIcon = hs.image.imageFromASCII([[ASCII:
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	1 . . . . . . . . . . . . . . 3
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. 4 . . . . . . . . . . . . 5 .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . . . . . . . . . . . . . .
	. . . 1 7 . . . . . . 6 3 . . .
]], {
	[4] = {
		fillColor = {
			red = 0.0,
			green = 0.0,
			blue = 1.0,
			alpha = 0.8,
		},
	},
})

function Water:new()
	self.enabled = false
	self.timer = hs.timer.new(30, self.waterAlert)

	self.menu = hs.menubar.new()
	self.menu:setClickCallback(function() self:toggleWaterAlert() end)
	self.menu:setIcon(self.inactiveIcon)

	return setmetatable({}, self)
end

function Water:toggleWaterAlert()
	if self.enabled then
		self:stop()
	else
		self:start()
	end
end

function Water:waterAlert()
	if hs.math.randomFloat() < 0.1 then
		hs.alert.show("Drink some water!", 6)
	end
end

function Water:start()
	self.timer:start()
	self.menu:setIcon(self.activeIcon, false)
	self.enabled = true
	hs.alert.show("Water Reminder: On", 2)
end

function Water:stop()
	self.timer:stop()
	self.menu:setIcon(self.inactiveIcon)
	self.enabled = false
	hs.alert.show("Water Reminder: Off", 2)
end

waterReminder = Water:new()

There’s probably more idiomatic ways to write this, but it works so I’ll take it.

It has two little flourishes I think are pretty neat:

  1. The timer fires 120 times an hour and each one has a 10% chance of triggering the alert. I wanted this because I find I’m sometimes able to subconsciously tune out alerts if they fire at set intervals. This works out to, on average, 12 alerts per hour (or one every five minutes). But sometimes more, sometimes less, sometimes 10 minutes between alerts, and sometimes only 1 minute between alerts.
  2. Those big blocks of ASCII art is how Hammerspoon can do icons! Using the ASCIIImage spec, I have an empty water glass when the timer is stopped and a mostly full water glass when the timer is running. If I’m on a call and sharing my screen, I wanted to be able to turn off the alerts easily.

Leave a comment

Your email address will not be published. Required fields are marked *