learning about qtile widgets via the medium of cricket

learning about qtile widgets via the medium of cricket

I’m a person who has spent an unreasonable amount of time making minute changes to the appearance of my bar in qtile. Despite the very nice selection of widgets availabe by default with qtile, it was only a matter of time before I decided I wanted to experiment with making my own custom widget. Fortunately, if you can do a bit of python this is quite an approachable undertaking.

The dream widget I was lacking was a little live crikcet score ticker type thing; something which would scroll along on my bar showing me the score in live cricket matches. I’m sure this will interest very few people but I learnt some good stuff along the way.

Hello World?!

As far as I can tell from looking at the code for some of the built-in widgets and this very helpful guide, a basic hello world widget would look something like this.

from libqtile.widget import base

class HelloWorld(base._TextBox):
    def __init__(self, **config):
        super().__init__("", **config)
        self.text = 'Hello world!'

Qtile provides a selection of base widget classes to do various useful things. This example uses the simple _TextBox which displays the content of the self.text property.

Hello World Version 2

Very nice, but for the cricket widget we’ll need something which lets us update the text. The answer is ThreadPoolText. This class lets you periodically update the text by overridding its poll method with a function that returns the updated text.

This example updates itself with hello world in a different language every five seconds.

import random

class HolaMundo(base.ThreadPoolText):

    defaults = [
        ("update_interval", 5, "Update interval for the widget"),
    ]

    def __init__(self, **config):
        super().__init__("", **config)
        self.add_defaults(HolaMundo.defaults)

    def poll(self):
        messages = [
            'Hola mundo',
            'Ciao mondo',
            'مرحبا بالعالم',
            'Saluton Mondo',
            'Sawubona Mhlaba'
        ]
        return random.choice(messages)

Along with the whole poll thing, this example also introduces defaults. This is a list of tuples which define parameters that the user can configure. Here we’ve added update_interval which defines how often the widget is updated.

Cricket Scores

For getting the cricket scores we’ll be using a handy rss feed from cricinfo so no brute force scraping will be required, just my beloved feedparser. This doesn’t involve anything directly widget related, just massaging the rss feed into something that would look useful on your bar. Anyway, this is what the finished item looked like.

from libqtile.widget import base
import feedparser

class CricketScores(base.ThreadPoolText):

    defaults = [
        ("update_interval", 60, "Update interval for the cricket scores widget"),
        ("teams",[],"Teams to display scores for"),
        ("separator"," \U0001F3CF ","Text to place between scores"),
        ("no_scores_string","","Text to show when there are no scores to show"),
    ]

    def __init__(self, **config):
        super().__init__("", **config)
        self.add_defaults(CricketScores.defaults)

    def get_scores(self):
        # parse rss feed and get 
        # live matches from title field
        feed = feedparser.parse("http://static.cricinfo.com/rss/livescores.xml")
        scores = []
        for match in feed.entries:
            # filter live matches
            if "*" in match.title:
                scores.append(match.title)

        # remove scores not involving chosen teams
        filtered_scores = []
        for score in scores:
            for team in self.teams:
                if team in score:
                    filtered_scores.append(score)

        # if no scores, show no_scores_string
        if len(filtered_scores) == 0:
            return self.no_scores_string 
        else:
            # form pretty string with separators
            final_scores = ""
            for score in filtered_scores:
                if score != filtered_scores[-1]:
                    final_scores += score + self.separator
                else:
                    final_scores += score
            return final_scores

    def poll(self):
        return get_scores()

Actaully Using It In Your Config

Assuming you have a my_widget.py file in the same directory as your config.py, you simply have to import it and add it your list of widgets.

from my_widget import LovelyWidget

screens = [
    Screen(
        top = bar.Bar(
            widgets = [
                LovelyWidget(),
                widget.GroupBox(),
            ]
        )
    )
]

Happy widget writing.