Making HackerTray

A few days back, I found the excellent HackerBarApp via Hacker News. Hacker News, for those of you who don’t know, is tech news website run by YCombinator. Hacker Bar was the simplest way of accessing HN stories that I’d ever seen. Unfortunately, it was only for Mac (made using rubymotion) and even though the source was available, it was of no use to me as a Linux User.

I decided to make a clone of Hacker Bar that would work on Linux. My first choice of the stack was node-webkit, an application framework that allows you to build cross-platform applications using HTML, CSS, JS, and modules from the node.js ecosystem. After reading a lot about node-webkit, I figured out that building this application in node-webkit (as it stands) would not be possible. Or rather, it would not work under Ubuntu and its derivatives because of lacking appindicator support in node-webkit. More details here.

The next obvious language and stack of choice was Python + Gtk. I’d already played a little bit with Gtk and Python some time back, so I knew the basics. But I’d never build a real application with PyGtk, just toys and small scripts. I found a basic skeleton app that was written for AppIndicator and modified it somewhat to form the base of HackerTray.

The next challenge I faced was keeping the check boxes always checked despite of any number of clicks after the first. That is, we don’t want any menu item to be “un-checked” at any moment. A basic idea is to do this (partial code):

def open(self, widget):
	if(widget.get_active() == False):

def addItem(self, item):
	#create a new CheckMenuItem (i)

However, this does not work as expected, because the widget.set_active() call also results in the activate event being fired, which ultimately calls open. This means on a click to an unchecked menuItem, the open function is called twice. This results in the browser opening the link twice.

As a workaround, I disabled the event handler in case it is a checked menuItem:

def open(self, widget):
	if(widget.set_active() == False)
		widget.signal_id = widget.connect('activate',

def addItem(self, item):
	#create a new CheckMenuItem (i)
	i.signal_id = i.connect('activate',

The next thing I worked on was a persistent memory for the app. In a nutshell, I needed to make sure that the tick on an item remained there, even if the app was restarted. This meant writing a list of all the “viewed” items into a file. After looking at shelve for a bit, I just rolled my own implementation , based on storing the data into ~/.hackertray.json file.

After that I worked on packaging the app into a python package, so that it could be easily installed. The python packaging tutorial was an easy to use guide that let me create the package easily and push it to the Python Package Index. A few issues in the package were found, and were fixed quickly thanks to the pull request by @brunal.

After improving the README a bit, I posted about it on Hacker News, where it failed to get any traction. I re-tried with a link to the HackerTray website, and that fell flat as well. It was on the next day, when I posted it to HN for the third time, that it took off. After 50 or so upvotes, I found that my instance refused to run because it had hit the API Rate Limit on the excellent node-hnapi. I quickly pushed a fix that used a list of servers to hit as fallback in case it crossed the Rate Limits.

After a lot of feedback from HN, I started work on a node-webkit based clone of hackertray for Windows. I should be able to release it in a few more days, if nothing else crops up. Keep watching this space for info. If you have any queries, just file an issue on GitHub or contact me.

Published on November 28, 2013