Note
This guide covers the new style of py3status modules. These are only available in version 3.0 and above.
Writing custom modules for py3status is easy. This guide will teach you how.
Let's start by looking at a simple example.
Here we start with the most basic module that just outputs a static string to the status bar.
# -*- coding: utf-8 -*-
"""
Example module that says 'Hello World!'
This demonstrates how to produce a simple custom module.
"""
class Py3status:
def hello_world(self):
return {
'full_text': 'Hello World!',
'cached_until': self.py3.CACHE_FOREVER
}
Save the file as hello_world.py
in a directory that
py3status will check for modules. By default it will look in
$HOME/.i3/py3status/
or you can specify additional directories using
--include
when you run py3status.
You need to tell py3status about your new module,
so in your i3status.conf
add:
order += "hello_world"
Then restart i3 by pressing Mod
+ Shift
+ R
. Your new module should now
show up in the status bar.
The Py3status
class tells py3status that this is a module. The module gets
loaded. py3status then calls any public methods that the class contains to get
a response. In our example there is a single method hello_world()
.
Read more here: [module methods](#module_methods).
The response that a method returns must be a python dict
.
It should contain at least two key / values.
This is the text that will be displayed in the status bar.
This tells py3status how long it should consider your
response valid before it should re-run the method to get a fresh response. In
our example our response will not need to be updated so we can use the special
self.py3.CACHE_FOREVER
constant. This tells py3status to consider our
response always valid.
cached_until
should be generated via the self.py3.time_in()
method.
This is a special object that gets injected into py3status
modules. It helps provide functionality for the module, such as the
CACHE_FOREVER
constant. Read more about the :ref:`py3`.
Allow users to supply configuration to a module.
# -*- coding: utf-8 -*-
"""
Example module that says 'Hello World!' that can be customised.
This demonstrates how to use configuration parameters.
Configuration parameters:
format: Display format (default 'Hello World!')
"""
class Py3status:
format = 'Hello World!'
def hello_world(self):
return {
'full_text': self.format,
'cached_until': self.py3.CACHE_FOREVER
}
This module still outputs 'Hello World' as before but now you can customise the
output using your i3status.config
for example to show the text in French.
hello_world {
format = 'Bonjour tout le monde!'
}
In your module self.format
will have been set to the value supplied in the
config.
Catch click events and perform an action.
# -*- coding: utf-8 -*-
"""
Example module that handles events
This demonstrates how to use events.
"""
class Py3status:
def __init__(self):
self.full_text = 'Click me'
def click_info(self):
return {
'full_text': self.full_text,
'cached_until': self.py3.CACHE_FOREVER
}
def on_click(self, event):
"""
event will be a dict like
{'y': 13, 'x': 1737, 'button': 1, 'name': 'example', 'instance': 'first'}
"""
button = event['button']
# update our output (self.full_text)
format_string = 'You pressed button {button}'
data = {'button': button}
self.full_text = self.py3.safe_format(format_string, data)
# Our modules update methods will get called automatically.
The on_click
method of a module is special and will get
called when the module is clicked on. The event parameter
will be a dict that gives information about the event.
A typical event dict will look like this:
{'y': 13, 'x': 1737, 'button': 1, 'name': 'example', 'instance': 'first'}
You should only receive events for the module clicked on, so generally we only care about the button.
The __init__()
method is called when our class is instantiated.
Note
__init__ is called before any config parameters have been set.
We use the safe_format()
method of py3
for formatting. Read more about
the :ref:`py3`.
Status string placeholders allow us to add information to formats.
# -*- coding: utf-8 -*-
"""
Example module that demonstrates status string placeholders
Configuration parameters:
format: Initial format to use
(default 'Click me')
format_clicked: Display format to use when we are clicked
(default 'You pressed button {button}')
Format placeholders:
{button} The button that was pressed
"""
class Py3status:
format = 'Click me'
format_clicked = 'You pressed button {button}'
def __init__(self):
self.button = None
def click_info(self):
if self.button:
data = {'button': self.button}
full_text = self.py3.safe_format(self.format_clicked, data)
else:
full_text = self.format
return {
'full_text': full_text,
'cached_until': self.py3.CACHE_FOREVER
}
def on_click(self, event):
"""
event will be a dict like
{'y': 13, 'x': 1737, 'button': 1, 'name': 'example', 'instance': 'first'}
"""
self.button = event['button']
# Our modules update methods will get called automatically.
This works just like the previous example but we can now be customised. The following example assumes that our module has been saved as click_info.py.
click_info {
format = "Cliquez ici"
format_clicked = "Vous avez appuyé sur le bouton {button}"
}
self.py3
in our module has color constants that we can access, these allow the user to set colors easily in their config.
Note
py3 colors constants require py3status 3.1 or higher
# -*- coding: utf-8 -*-
"""
Example module that uses colors.
We generate a random number between and color it depending on its value.
Clicking on the module will update it an a new number will be chosen.
Configuration parameters:
format: Initial format to use
(default 'Number {number}')
Format placeholders:
{number} Our random number
Color options:
color_high: number is 5 or higher
color_low: number is less than 5
"""
from random import randint
class Py3status:
format = 'Number {number}'
def random(self):
number = randint(0, 9)
full_text = self.py3.safe_format(self.format, {'number': number})
if number < 5:
color = self.py3.COLOR_LOW
else:
color = self.py3.COLOR_HIGH
return {
'full_text': full_text,
'color': color,
'cached_until': self.py3.CACHE_FOREVER
}
def on_click(self, event):
# by defining on_click pressing any mouse button will refresh the
# module.
pass
The colors can be set in the config in the module or its container or in the
general section. The following example assumes that our module has been saved
as number.py
. Although the constants are capitalized they are defined in the
config in lower case.
number {
color_high = '#FF0000'
color_low = '#00FF00'
}
Py3status will call a method in a module to provide output to the i3bar. Methods that have names starting with an underscore will not be used in this way. Any methods defined as static methods will also not be used.
Output methods should provide a response dict.
Example response:
{
'full_text': "This text will be displayed",
'cached_until': 1470922537, # Time in seconds since the epoch
}
The response can include the following keys
cached_until
The time (in seconds since the epoch) that the output will be classed as no longer valid and the output function will be called again.
Since version 3.1, if no cached_until
value is provided the output will
be cached for cache_timeout
seconds by default this is 60
and can be
set using the -t
or --timeout
option when running py3status. To never
expire the self.py3.CACHE_FOREVER
constant should be used.
cached_until
should be generated via the self.py3.time_in()
method.
color
The color that the module output will be displayed in.
composite
Used to output more than one item to i3bar from a single output method. If this is provided then full_text
should not be.
full_text
This is the text output that will be sent to i3bar.
index
The index of the output. Allows composite output to identify which component of their output had an event triggered.
separator
If False
no separator will be shown after the output block (requires i3bar
4.12).
urgent
If True
the output will be shown as urgent in i3bar.
Some special method are also defined.
kill()
Called just before a module is destroyed.
on_click(event)
Called when an event is received by a module.
post_config_hook()
Called once an instance of a module has been created and the configuration
parameters have been set. This is useful for any work a module must do before
its output methods are run for the first time. post_config_hook()
introduced in version 3.1
Py3 is a special helper object that gets injected into py3status modules, providing extra functionality. A module can access it via the self.py3 instance attribute of its py3status class. For details see :ref:`py3`.
Whilst most modules return a simple response eg:
{
'full_text': <some text>,
'cached_until': <cache time>,
}
Sometimes it is useful to provide a more complex, composite response. A composite is made up of more than one simple response which allows for example a response that has multiple colors. Different parts of the response can also be differentiated between when a click event occurs and so allow clicking on different parts of the response to have different outcomes. The different parts of the composite will not have separators between them in the output so they will appear as a single module to the user.
The format of a composite is as follows:
{
'cached_until': <cache time>,
'composite': [
{
'full_text': <some text>,
},
{
'full_text': <some more text>,
'index': <some index>
},
]
}
The index
key in the response is used to identify the individual block and
when the modules on_click()
method is called the event will include this.
Supplied index values should be strings. If no index is given then it will
have an integer value indicating its position in the composite.
Py3status allows modules to maintain state through the use of the storage functions of the Py3 helper.
Currently bool, int, float, None, unicode, dicts, lists, datetimes etc are supported. Basically anything that can be pickled. We do our best to ensure that the resulting pickles are compatible with both python versions 2 and 3.
The following helper functions are defined in the modules :ref:`py3`.
These functions may return None
if storage is not available as well as some
metadata such as storage creation timestamp _ctime
and
last modification timestamp _mtime
.
Example:
def module_function(self):
# set some storage
self.py3.storage_set('my_key', value)
# get the value or None if key not present
value = self.py3.storage_get('my_key')
All contributed modules should have correct documentation. This documentation is in a specific format and is used to generate user documentation.
The docstring of a module is used. The format is as follows:
- Single line description of the module followed by a single blank line.
- Longer description of the module providing more detail.
- Configuration parameters. This section describes the user settable
parameters for the module. All parameters should be listed (in alphabetical
order). default values should be given in parentheses eg
(default 7)
. - Format placeholders. These are used for substituting values in format strings. All placeholders should be listed (in alphabetical order) and describe the output that they provide.
- Color options. These are the color options that can be provided for this module. All color options should be listed (in alphabetical order) that the module uses.
- Requires. A list of all the additional requirements for the module to work. These may be command line utilities, python libraries etc.
- Example. Example configurations for the module can be given.
- Author and license. Finally information on the modules author and a license can be provided.
Here is an example of a docstring.
"""
Single line summary
Longer description of the module. This should help users understand the
modules purpose.
Configuration parameters:
parameter: Explanation of this parameter (default <value>)
parameter_other: This parameter has a longer explanation that continues
onto a second line so it is indented.
(default <value>)
Format placeholders:
{info} Description of the placeholder
Color options:
color_meaning: what this signifies, defaults to color_good
color_meaning2: what this signifies
Requires:
program: Information about the program
python_lib: Information on the library
Example:
```
module {
parameter = "Example"
parameter_other = 7
}
```
@author <author>
@license <license>
"""
Sometimes it is necessary to deprecate configuration parameters. Modules are able to specify information about deprecation so that it can be done automatically. Deprecation information is specified in the Meta class of a py3status module using the deprecated attribute. The following types of deprecation are supported.
The deprecation types will be performed in the order here.
rename
The parameter has been renamed. We will update the configuration to use the new name.
class Py3status:
class Meta:
deprecated = {
'rename': [
{
'param': 'format_available', # parameter name to be renamed
'new': 'icon_available', # the parameter that will get the value
'msg': 'obsolete parameter use `icon_available`', # message
},
],
}
format_fix_unnamed_param
Some formats used {}
as a placeholder this needs to be updated to a named
placeholder eg {value}
.
class Py3status:
class Meta:
deprecated = {
'format_fix_unnamed_param': [
{
'param': 'format', # parameter to be changed
'placeholder': 'percent', # the place holder to use
'msg': '{} should not be used in format use `{percent}`', # message
},
],
}
rename_placeholder
We can use this to rename placeholders in format strings
class Py3status:
class Meta:
deprecated = {
'rename_placeholder': [
{
'placeholder': 'cpu', # old placeholder name
'new': 'cpu_usage', # new placeholder name
'format_strings': ['format'], # config settings to update
},
],
}
update_placeholder_format
This allows us to update the format of a placeholder in format strings.
The key value pairs {placeholder: format} can be supplied as a dict in
placeholder_formats
or the dict can be provided by function
the
function will be called with the current config and must return a dict.
If both are supplied then placeholder_formats
will be updated using
the dict supplied by the function.
class Py3status:
class Meta:
deprecated = {
'update_placeholder_format': [
{
'function': update_placeholder_format, # function returning dict
'placeholder_formats': { # dict of placeholder:format
'cpu_usage': ':.2f',
},
'format_strings': ['format'], # config settings to update
}
],
}
substitute_by_value
This allows one configuration parameter to set the value of another.
class Py3status:
class Meta:
deprecated = {
'substitute_by_value': [
{
'param': 'mode', # parameter to be checked for substitution
'value': 'ascii_bar', # value that will trigger the substitution
'substitute': {
'param': 'format', # parameter to be updated
'value': '{ascii_bar}', # the value that will be set
},
'msg': 'obsolete parameter use `format = "{ascii_bar}"`', #message
},
],
}
function
For more complex substitutions a function can be defined that will be called with the config as a parameter. This function must return a dict of key value pairs of parameters to update
class Py3status:
class Meta:
# Create a function to be called
def deprecate_function(config):
# This function must return a dict
return {'thresholds': [
(0, 'bad'),
(config.get('threshold_bad', 20), 'degraded'),
(config.get('threshold_degraded', 50), 'good'),
],
}
deprecated = {
'function': [
{
'function': deprecate_function, # function to be called
},
],
}
remove
The parameters will be removed.
class Py3status:
class Meta:
deprecated = {
'remove': [
{
'param': 'threshold_bad', # name of parameter to remove
'msg': 'obsolete set using thresholds parameter', #message
},
],
}
Sometimes it is necessary to update configuration parameters. Modules are able to specify information about updates so that it can be done automatically. Config updating information is specified in the Meta class of a py3status module using the update_config attribute. The following types of updates are supported.
update_placeholder_format
This allows us to update the format of a placeholder in format strings.
The key value pairs {placeholder: format} can be supplied as a dict in
placeholder_formats
or the dict can be provided by function
the
function will be called with the current config and must return a dict.
If both are supplied then placeholder_formats
will be updated using
the dict supplied by the function.
This is similar to the deprecation method but is to allow default formatting of placeholders to be set.
In a module like sysdata we have placeholders eg {cpu_usage}
this ends up
having a value something like 20.542317173377157
which is strange as the
value to use but gives the user the ability to have as much precision as they
want. A module writer may decide that they want this displayed as 20.54
so
{cpu_usage:.2f}
would do this. Having a default format containing that
just looks long/silly and the user setting a custom format just wants to do
format = 'CPU: {cpu_usage}%'
and get expected results ie not the full
precision. If they don't like the default formatting of the number they could
still do format = 'CPU: {cpu_usage:d}%' etc.
So using this allows sensible defaults formatting and allows simple placeholders for user configurations.
class Py3status:
class Meta:
update_config = {
'update_placeholder_format': [
{
'placeholder_formats': { # dict of placeholder:format
'cpu_usage': ':.2f',
},
'format_strings': ['format'], # config settings to update
}
],
}
Each module should be able to run independently for testing purposes. This is simply done by adding the following code to the bottom of your module.
if __name__ == "__main__":
"""
Run module in test mode.
"""
from py3status.module_test import module_test
module_test(Py3status)
If a specific config should be provided for the module test, this can be done as follows.
if __name__ == "__main__":
"""
Run module in test mode.
"""
config = {
'always_show': True,
}
from py3status.module_test import module_test
module_test(Py3status, config=config)
Such modules can then be tested independently by running
python /path/to/module.py
.
$ python loadavg.py
[{'full_text': 'Loadavg ', 'separator': False,
'separator_block_width': 0, 'cached_until': 1538755796.0},
{'full_text': '1.87 1.73 1.87', 'color': '#9DD7FB'}]
^C
We also can produce an output similar to i3bar output in terminal with
python /path/to/module.py --term
.
$ python loadavg.py --term
Loadavg 1.41 1.61 1.82
Loadavg 1.41 1.61 1.82
Loadavg 1.41 1.61 1.82
^C