bumblebee-status/doc/development/WRITING-A-MODULE.md
2020-05-03 16:47:53 +02:00

4.6 KiB

Writing a bumblebee-status module

Introduction

Adding a new module to bumblebee-status is straight-forward:

  • Add a new Python module in modules/contrib/. The name of the module will be the name that the user needs to specify when invoking bumblebee-status (i.e. a module called modules/contrib/test.py will be loaded using bumblebee-status -m test)
  • See below for how to actually write the module
  • Test (run bumblebee-status in the CLI)
  • Make sure your changes don't break anything: ./coverage.sh
  • If you want to do me favour, run your module through black -t py34 before submitting

Pull requests

The project gladly accepts PRs for bugfixes, new functionality, new modules, etc. When you feel comfortable with what you've developed, please just open a PR, somebody will look at it eventually :) Thanks!

Coding guidelines

I'm pretty open to whatever style you use, but if it's all the same to you (and yes, I know that the current codebase is only slowly adapting to this):

  • Please favour single quotes for strings (except for docstrings, which are always """)
  • For private methods/variables, please use a leading __ (e.g. __output rather than _output)

Hello world

This example will show "hello world" in the status bar:

"""Short description"""

import core.module
import core.widget

class Module(core.module.Module):
    def __init__(self, config):
        super().__init__(config, core.widget.Widget(self.full_text))

    def full_text(self, widgets):
        return 'hello world'

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

Of modules and widgets

There are two important concepts for module writers:

  • A module is something that offers a single set of coherent functionality
  • A module has 1 to n "widgets", which translates to individual blocks in the i3bar

Very often, this is a 1:1 relationship, and a single module has a single widget. If that's the case for you, you can stop reading now :)

Otherwise, you have a number of ways to handle widgets:

  • During the super().init__(...) inside the module's constructor, you can specify a list of widgets, and those will comprise the widgets (in ordered fashion)
  • During runtime, you can set a new list of widgets by using the self.widgets(<new list>) method of the module

Adding widgets at runtime

If you want to add widgets during runtime, please use the add_widget() method of the module:

def do_something(self):
	self.add_widget(full_text="my sample text", name="<optional name>")

TODO: expand on this

Periodic updates

bumblebee-status modules have two different ways to update their data:

  1. Each interval, the callback registered when the widget was created is called. You can do arbitrarily complex things there
  2. Each interval, before the widget's callback is invoked, a generic update(self, widgets) method is called on the module

Largely, where you want to put your update code is up to you. My observations:

  • If you want to change the widgets a module has, you have to stick with update()
  • For simple modules, doing the data update in the widget callback is simplest (see kernel, for example)

Advanced topics

Event handlers

The core.input module can be used to execute callbacks during mouse events:

import core.module
import core.widget
import core.input

class Module(core.module.Module):
    @core.decorators.every(minutes=60, seconds=20)
    def __init__(self, config):
        super().__init__(config=config, widgets=<widgets>)

		core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=<cmd>)

The command can be either a CLI tool that will be directly executed (e.g. cmd='shutdown -h now') or a method that will be executed. The method's signature needs to be: def <name>(self, event), where "event" is the event data provided by i3wm.

The full list of possible bindings:

  • LEFT_MOUSE
  • RIGHT_MOUSE
  • MIDDLE_MOUSE
  • WHEEL_UP
  • WHEEL_UP

Setting a default update interval

To change the default update interval, you can use a simple decorator:

import core.module
import core.widget
import core.decorators

class Module(core.module.Module):
    @core.decorators.every(minutes=60, seconds=20)
    def __init__(self, config):
        super().__init__(config=config, widgets=<widgets>)

NOTE: This makes the update interval of the module independent of what the user configures via -i <interval>! It is still possible to override the module's interval using -p <module>.interval=<value>, however.

TODOs

  • default update interval
  • scrolling
  • theme.minwidth
  • scrolling decorator
  • theme.exclude
  • per module update interval -> nice string format
  • update via events