4 Hugues Ross - Blog: 11/01/2017 - 12/01/2017
Hugues Ross


Playing with Cairo

This week, I took a break from Singularity to mess around with my desktop a bit. In the process of that, I finally sat down and tried using the Cairo vector graphics library.

The Goal

Right now, I'm using a fork of the i3lock utility to lock my PC. Honestly, I'm not thrilled by how it looks. It basically just displays a simple indicator over an image you provide (or a blurred version of your desktop), and that's it. Here's an example from the Github page:
Lately, I've been thinking that the overall "circle with some text in it" design it looks kinda tacky. Unfortunately, I don't know of many other screen-locking options. So, I thought I'd make a small fork that provides a more interesting overlay. I have a lot of ideas for fun designs and overlays that I could use to replace the old design, but I decided to start with something basic:
Note: Colors and fonts aren't final. Just needed something to put in this mockup
I figured that a simple static design would would be pretty easy to swap in, and then I could look into some crazier ideas like dynamic overlays and procedural generation.

Current Progress

To get a new overlay working, the obvious first step is to figure out how the old overlay works. After some poking in the code, it looks like i3lock renders its overlay via Cairo, so I finally decided to finally learn the API.

Cairo is one of those things that I've known about for years, but never really had a good excuse to play with. This mini-project gave me the perfect opportunity, so I decided to make a little playground application where I could mess around. To make things extra-simple, I also did it in Vala. I'm still not sure if I want to try and hook vala-generated code up to the final product, but it's a big help in the short term. After a couple hours of playing around, I made a function to draw those colored parallelograms (which I called lozenges in code, because only a sadist would casually drop a word of that length into a function name):

   53     public void draw_lozenge(int x, int y, int width, int height, int spacing, float[] ratio)
   54         requires(ratio.length >= 3)
   55     {
   56         ctx.move_to(x, y);
   57         ctx.new_path();
   58         ctx.line_to(x + (width * ratio[0]), y);
   59         ctx.line_to(x + (width * ratio[0]) - (height), y + height);
   60         ctx.line_to(x, y + height);
   61         ctx.line_to(x, y);
   62         ctx.close_path();
   63         ctx.set_source_rgba (147.0f / 255, 87.0f / 255, 57.0f / 255, 1);
   64         ctx.fill ();
   66         ctx.new_path();
   67         ctx.move_to(x + (width * ratio[0]) + spacing, y);
   68         ctx.line_to(x + (width * ratio[0]) + spacing + (width * ratio[1]), y);
   69         ctx.line_to(x + (width * ratio[0]) + spacing - (height) + (width * ratio[1]), y + height);
   70         ctx.line_to(x + (width * ratio[0]) + spacing - (height), y + height);
   71         ctx.set_source_rgba (214.0f / 255, 165.0f / 255, 122.0f / 255, 1);
   72         ctx.fill ();
   75         ctx.new_path();
   76         ctx.move_to(x + (width * ratio[0]) + (width * ratio[1]) + (spacing * 2), y);
   77         ctx.line_to(x + (width * ratio[0]) + (width * ratio[1]) + (spacing * 2) + (width * ratio[2]), y);
   78         ctx.line_to(x + (width * ratio[0]) + (width * ratio[1]) + (spacing * 2) - (height) + (width * ratio[2]), y + height);
   79         ctx.line_to(x + (width * ratio[0]) + (width * ratio[1]) + (spacing * 2) - (height), y + height);
   80         ctx.set_source_rgba (161.0f / 255, 124.0f / 255, 91.0f / 255, 1);
   81         ctx.fill ();
   82     }

I am 100% certain that this code can be simplified further and made more generic, but for the moment I'm happy enough with the result.

Besides the missing text, this is almost exactly the same. The big difference is that whereas the previous image was painstakingly hand-made, this new version is the result of 2 simple function calls.

What's Next?

Naturally, the next steps are to add text and get the new design into i3lock. Cairo provides some text-rendering methods (i3lock uses these already), and I believe there's also a library called PangoCairo that adds rich text/formatting/markup via Pango. I haven't tried either of these approaches yet, but I will soon enough.

Once I have a working i3lock with a custom design, I'll post again and look at some of the interesting options that this opens up for me.


Singularity: It's the Little Things

It's been a few weeks since I wrote about some of my old mistakes working on Singularity, and I think it's time for a little follow-up. I've implemented all of the changes that I mentioned (and they work), so I'll be discussing two "smaller" features that I've been working on and what I've learned by working on them.

Look, Ma, no Feedback!

One big problem that I've had in this new version of Singularity is how opaque it is about everything. Before I started working on it again, there was no way to see if any feeds failed to load, or where feeds pointed to. As a result, a feed could go down and silently fail for months while you were none the wiser! Since I'd identified this problem before I started work again, I was already planning to do something about it. To keep things simple, I'm just making the text in the feed sidebar stand out more for now.

So that's the first piece of this puzzle. However, there remains one big problem: What do you do when you know a feed doesn't load? I knew that I had a large number of broken feeds, but without somewhere to see the url it would be difficult to know if the feed had moved or been deleted, or if it was just a parser error.

I'm still mulling over the best solution to the problem. For the moment, I've created a properties page for feeds that contains info like the title and url. This works fine, although it could still use some polish. Another feature that I'm considering is an 'update status' indicator that could display detailed update progress and error messages. Either way, it's clear that Singularity needs to provide more feedback when things fail.

Lesson Learned: Detailed feedback can be very important, and not just as polish!

Getting Organi--Wait, Wrong Series

I'm nearly done with a new feature that, apparently, is one of the most important things to add. When I started rewriting Singularity a while back, I wanted to add folder-like entries to the sidebar so that users could organize their feeds. This seemed like more of a "would be nice" feature than an absolutely vital one, so it was left in a half-finished state.

Boy, was I wrong with this one. I decided to dedicate the weekend to finishing the feature, and it turns out to be a complete game-changer. I didn't expect it to be important, because I mostly just checked my feeds via the 'All Feeds' view. However, I had my logic backwards. I was checking the 'All Feeds' view because there was no other way, not out of preference. I figured this out after sorting all of my feeds into collections, and seeing a result like this:
In the left image, you know nothing other than the fact that there are 3 unread items of some description. What are they? How long will it take to read them? Do I care right now? The program has no answer for you, because the feeds with new items are hiding under 100 others. Even if they were at the top, you could have 30 feeds with one update each. Instead of hunting around, the most obvious solution is always just to click 'All Feeds' and read through everything.

The picture on the right gives much more info. Right off the bat, you know that 3 of the new updates are news articles. If you're about to head out then you probably don't want to check them yet, and you don't have to. It suddenly makes a lot of sense to check the categories that interest you, and leave the stuff you don't want to check for later. I'm glad to have this new feature, but I'm kicking myself over how many months I suffered without it.

Now that it actually makes sense to check things that aren't 'All Feeds', I've also added a small quality-of-life feature: Item views now display the feed/collection that they correspond to. It's a minor thing, but it's nice to have.

Lesson Learned: Just because the user always does something doesn't mean that it's the only way they ever will. Some habits may be a result of weak design.


For the first time in quite a while, I feel happy and optimistic regarding Singularity's future. My last rewrite ending halfway put me in a spot where I was unhappy with the result, but the polish and new features feel like a much-needed breath of fresh air. My goal of a new release by the year's end is starting to look much more realistic!