I’m a freelance software developer. As a lifelong scatterbrain, the part of my job I hate the most is time tracking. Maybe this particular personal failing ought to have precluded me from becoming a freelancer but everything seems to be working out okay.
I bill clients by the hour, and I don’t want to either (a) rip the piss or (b) shortchange myself. But when it comes time to send invoices and I realise I have been delinquent in recording my hours, of course I err on the side of the latter.
The accounting software I use (freeagent) has a handy web / app based time entry system complete with timers, but I’ve always found having to log in and mess around with a web form a bit … listen, I don’t want to make excuses. It’s not you, it’s me.
Anyway, the time has come for me to sort myself out. Enter my super niche January side project: fatt, a rofi-based freeagent timer control!
The gist of the video above, if you can’t or won’t watch it, is that I can now create, start and stop FreeAgent timers in a custom rofi mode.
Interesting bits
Learning C
As a total newbie to C (save for some fannying about with the odd microcontroller), writing a plugin for rofi was a pretty straightforward and practical way to jump in. qball’s blog post was helpful and enlightening, as was examining the code of a few other plugins (calc, file-browser-extended, blocks).
That said, I had given myself a fairly firm deadline on this (24th Jan) so I cheesed it a wee bit by deciding that the rofi plugin would just be a frontend for a CLI app that would do the heavy OAuth2 / network lifting.
The documentation for glib is extensive and it will probably be my starting point for any future non-microcontroller C project. Since I was writing a frontend for a CLI, I determined quickly that I wanted to fork, and I would need g_spawn_async_with_pipes. The best example code I found was in Gnome itself, specifically for using passwd as a CLI backend for the gnome-control-center UI.
I stripped that back into something general purpose, that is used like this:
Then, in my callback functions, I read the CLI output line by line into an array of “rows” to be displayed by rofi. I could really define that however I like – the rofi plugin ABI just asks me to define some functions along the lines of (pseudocode) get_num_entries
and get_display_value(entryId)
– so I opted to define a Row struct which also stores the freeagent object ID relating to each row.
OAuth2 client on the CLI
FreeAgent’s API can be accessed through OAuth2 only, I can’t just use account-specific secret tokens to access my own account. There is probably a reasonable excuse for this, although I note FreeAgent itself appears to be hosted on AWS – presumably there’s a dev somewhere in Edinburgh with a file in the format aws_access_key_id:aws_secret_access_key
in their /etc folder that would allow them to do what they want with my data.
So anyway, I have to register as a developer, register an app and use localhost as the callback URL for that app. When my cli app goes to get bearer and refresh tokens, it has to launch an HTTP server which will parse the tokens from GET parameters. I do it like this:
Displaying my daily “money earned” in i3bar
Well, why not? The CLI command takes a list of the current day’s timeslips and multiplies them by the hourly rate of their related task. i3status is then piped through a script which prepends this figure to my other vital stats – wifi speed, load average, etc.
I have some ideas for this that are as yet unrealised – for example, on the easy side, it could turn green when a timer is on. Or, on the very hard side, something could detect I’ve had twitter open for 5 minutes and automatically stop a timer, turning my status bar red and flashing or something. Don’t know where to start with that one, hints in the comments please.
Some people have informed me that this is not only depressingly capitalistic, but also psychologically inferior to a progress bar. That is why I also submitted a pull request to i3status to make their example perl script chainable – so I could return a vague promise to also show a burndown sparkline for any serious side project or savings goal that I start working on. I also reduced the refresh rate to once a minute as it definitely felt a bit psychopathic to be getting a visual update every time I made seven pence.
Postponing learning Golang, for now
I’ve already used the deadline I set as an excuse for writing a CLI at all, but I actually originally intended to write the CLI using Golang – yet another language I’ve intended to learn properly for a long time. But the barrier of trying to set up the whole temporary HTTP server for OAuth2 in a language I’m not familiar with was frustrating enough that I decided that I would be better doing it quickly in PHP, since it would be very fast and let me get to the fun stuff (the C bit).
How do I feel after a week?
A week may be too soon to tell, but the rapid ability to toggle timers, combined with the cash counter in the status bar, gave me a real sense of either being “on” or “off”. That discouraged procrastination, which in itself was a good outcome that encouraged me to be diligent with the timer controls. As a result I had a pretty good, productive week and also all the boring timesheet admin did not need rushed in at 5pm on Friday (or 10am the following Monday). So I am pretty pleased with it.
Closing thoughts
In general I would always encourage anyone to spend time writing software that will only benefit themselves. At least for me, this sort of personal tool creation is the genuinely interesting thing about computer programming, way more than writing code in the service of our bosses’ attempts to generate economic value.
More specifically I can really recommend a rofi plugin as a really handy way to get input / listing / filtering taken care of for a productivity utility. If you see rofi as some kind of a super limited GUI framework it takes on quite an interesting shape, with lots of creative possibilities.