# 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`

## 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:

```python
"""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

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:
```python
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:
```python
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