Skip to content

Latest commit

 

History

History
425 lines (292 loc) · 13.1 KB

user-guide.rst

File metadata and controls

425 lines (292 loc) · 13.1 KB

User guide

Installation

Before installing, it is recommended to create a venv to isolate i3pyblocks (and its dependencies) from other Python packages in your system [1]. To do it, you can run:

$ python3 -m venv venv
$ source venv/bin/activate

To actually install i3pyblocks, make sure you have Python >=3.7 installed and simply run this simple command in your terminal of choice:

$ python3 -m pip install i3pyblocks

This will install a basic installation without dependencies, so most blocks will not work. Check options.extras_require section in setup.cfg to see the current available optional dependencies for each block.

For example, if you want to use :mod:`i3pyblocks.blocks.pulse` you will need to install the dependencies listed in blocks.pulse. It is very easy to do this using pip itself:

$ python3 -m pip install 'i3pyblocks[blocks.pulse]'

You can also pass multiple blocks dependencies at the same time:

$ python3 -m pip install 'i3pyblocks[blocks.dbus,blocks.i3ipc,blocks.inotify]'

If you want to install the latest version from git, you can also run something similar to below:

$ python3 -m pip install -e 'git+https://github.com/thiagokokada/i3pyblocks#egg=i3pyblocks[blocks.i3ipc,blocks.ps]'
.. seealso::

   If you're using `NixOS`_ or nixpkgs, check `nix-overlay`_ branch for an
   alternative way to install using `Nix overlays`_.

[1]Other options are pipx, poetry or pipenv. Use the solution you feel most confortable to use.

Configuring your i3pyblocks

Let's start with a basic configuration showing a simple text (:class:`~i3pyblocks.blocks.basic.TextBlock`) and a clock (:class:`~i3pyblocks.blocks.datetime.DateTimeBlock`):

import asyncio

from i3pyblocks import core, utils
from i3pyblocks.blocks import basic, datetime


async def main():
    runner = core.Runner()
    await runner.register_block(basic.TextBlock("Welcome to i3pyblocks!"))
    await runner.register_block(datetime.DateTimeBlock())

    await runner.start()


asyncio.run(main())

In the code above we are creating a new :class:`~i3pyblocks.core.Runner` instance, the most important class in i3pyblocks, responsible to manage blocks, update the i3bar, receive signal and mouse clicks, etc. To register a block we need to call :meth:`~i3pyblocks.core.Runner.register_block` with a instance of :class:`~i3pyblocks.blocks.base.Block` as the first parameter. We call two separate blocks here, :class:`~i3pyblocks.blocks.basic.TextBlock` and :class:`~i3pyblocks.blocks.datetime.DateTimeBlock`.

Save the content above in a file called config.py. To test in terminal, we can run it using:

$ i3pyblocks -c config.py

Running this for ~5 seconds in terminal. You can press Ctrl+C to stop (you may) need to press twice to exit:

{"version": 1, "click_events": true}
[
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "Welcome to i3pyblocks!"}, {"name": "DateTimeBlock", "instance": "<random-id>", "full_text": "18:02:50"}],
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "Welcome to i3pyblocks!"}, {"name": "DateTimeBlock", "instance": "<random-id>", "full_text": "18:02:51"}],
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "Welcome to i3pyblocks!"}, {"name": "DateTimeBlock", "instance": "<random-id>", "full_text": "18:02:52"}],
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "Welcome to i3pyblocks!"}, {"name": "DateTimeBlock", "instance": "<random-id>", "full_text": "18:02:53"}],
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "Welcome to i3pyblocks!"}, {"name": "DateTimeBlock", "instance": "<random-id>", "full_text": "18:02:54"}],
^C

Now, to start using it in your i3wm, add it to your $HOME/.config/i3/config file (or $HOME/.config/sway/config if using sway):

bar {
    position top
    status_command i3pyblocks -c /path/to/your/config.py
}

Or, if using a venv:

bar {
    position top
    status_command /path/to/venv/bin/i3pyblocks -c /path/to/your/config.py
}

Customizing blocks

Most blocks can be customized by passing optional parameters to its constructor. Let's say that you want to use a custom formatting to show date and time in :class:`~i3pyblocks.blocks.datetime.DateTimeBlock`, and use a white background instead of the default one. You can do something like this:

import asyncio

from i3pyblocks import core, utils
from i3pyblocks.blocks import datetime


async def main():
    runner = core.Runner()
    await runner.register_block(
        datetime.DateTimeBlock(
            format_date="%Y-%m-%d",
            format_time="%H:%M:%S",
            default_state={"background": "#FFFFFF"},
        )
    )

    await runner.start()


asyncio.run(main())

Running this for ~5 seconds in terminal results:

{"version": 1, "click_events": true}
[
[{"name": "DateTimeBlock", "instance": "<random-id>", "background": "#FFFFFF", "full_text": "19:57:09"}],
[{"name": "DateTimeBlock", "instance": "<random-id>", "background": "#FFFFFF", "full_text": "19:57:10"}],
[{"name": "DateTimeBlock", "instance": "<random-id>", "background": "#FFFFFF", "full_text": "19:57:11"}],
[{"name": "DateTimeBlock", "instance": "<random-id>", "background": "#FFFFFF", "full_text": "19:57:12"}],
[{"name": "DateTimeBlock", "instance": "<random-id>", "background": "#FFFFFF", "full_text": "19:57:13"}],
^C

default_state receives any value allowed by the i3bar's protocol and sets it in the result, unless it is overwritten by the :meth:`~i3pyblocks.blocks.base.Block.update_state` method inside the block. So it is a good place to use custom formatting to make your block unique.

It is strongly recommended that you use keyword parameters in constructors (i.e.: format_date="%Y-%m-%d") instead of positional parameters (i.e.: only "%Y-%m-%d"), since this will make your configuration clearer and avoid breakage in the future.

Most packages uses an extended version of Python's format for formatting strings, :class:`~i3pyblocks.formatter.ExtendedFormatter`, allowing a very good degree of customization, for example:

import asyncio

from i3pyblocks import core, utils
from i3pyblocks.blocks import ps


async def main():
    runner = core.Runner()
    await runner.register_block(ps.VirtualMemoryBlock(format="{available}G"))
    await runner.register_block(ps.VirtualMemoryBlock(format="{available:.1f}G"))

    await runner.start()


asyncio.run(main())

Running this in terminal, results:

$ i3pyblocks -c config.py
{"version": 1, "click_events": true}
[
[{"name": "VirtualMemoryBlock", "instance": "<random-id>", "full_text": "9.517715454101562G"}, {"name": "VirtualMemoryBlock", "instance": "<random-id>", "full_text": "9.5G"}],
^C

If you want greater customization than what is available with a block constructor parameters, you can always extend the class:

import asyncio
from datetime import datetime, timezone

from i3pyblocks import core, utils
from i3pyblocks.blocks import datetime as m_datetime


class CustomDateTimeBlock(m_datetime.DateTimeBlock):
    async def run(self) -> None:
        utc_time = datetime.now(timezone.utc)
        self.update(utc_time.strftime(self.format))

async def main():
    runner = core.Runner()
    await runner.register_block(CustomDateTimeBlock())

    await runner.start()


asyncio.run(main())

Using Pango markup

Using Pango markup allows for greater customization of text. It is basically a simplified version of HTML, including tags that allow you to make show in a different font, in bold or italic, increase or decrease the size, etc.

While it is possible to create the Pango markup manually, using :func:`i3pyblocks.utils.pango_markup` make things much easier. For example:

import asyncio

from i3pyblocks import core, utils, types
from i3pyblocks.blocks import basic


async def main():
    runner = core.Runner()
    await runner.register_block(
        basic.TextBlock(
            utils.pango_markup("Welcome to i3pyblocks!", font_size="large"),
            markup=types.MarkupText.PANGO
        )
    )

    await runner.start()


asyncio.run(main())

Running this in terminal:

$ i3pyblocks -c config.py
{"version": 1, "click_events": true}
[
[{"name": "TextBlock", "instance": "<random-id>", "full_text": "<span font_size=\"large\">Welcome to i3pyblocks!</span>", "markup": "pango"}],
^C

Use Pango markup with the i3pyblocks placeholders to archive the same effect even with dynamic text:

import asyncio

from i3pyblocks import core, utils, types
from i3pyblocks.blocks import ps


async def main():
    runner = core.Runner()
    await runner.register_block(
        ps.LoadAvgBlock(
            format=utils.pango_markup("{load1}", font_weight="heavy"),
            default_state={"markup": types.MarkupText.PANGO},
        )
    )

    await runner.start()


asyncio.run(main())

Warning

The Pango markup requires a Pango font. Make sure you configured i3bar to use a Pango font. For example:

font pango:Inconsolata, Icons 12

Clicks and signals

If you want some block to react to signals, you need to register them first by passing signals parameter to :meth:`~i3pyblocks.core.Runner.register_block`:

import asyncio
import signal

from i3pyblocks import core, utils
from i3pyblocks.blocks import datetime


async def main():
    runner = core.Runner()
    await runner.register_block(
        datetime.DateTimeBlock(
            format_date="%Y-%m-%d",
            format_time="%H:%M:%S",
        ),
        signals=(signal.SIGUSR1, signal.SIGUSR2)
    )

    await runner.start()


asyncio.run(main())

This only allow :class:`~i3pyblocks.blocks.datetime.DateTimeBlock` to receive SIGUSR1 and SIGUSR2 signals, it does not necessary handle them. Of course, most blocks already have some default handler for them (i.e.: for most blocks it triggers a force refresh), but in case you want something else you can override :meth:`~i3pyblocks.blocks.base.Block.signal_handler`:

import asyncio
import signal

from i3pyblocks import core, utils
from i3pyblocks.blocks import datetime


class CustomDateTimeBlock(datetime.DateTimeBlock):
    async def signal_handler(self, *, sig: signal.Signals) -> None:
        if sig == signal.SIGUSR1:
            self.format = self.format_time
        elif sig == signal.SIGUSR2:
            self.format = self.format_date
        # Calling the run method here so the block is updated immediately
        self.run()

async def main():
    runner = core.Runner()
    await runner.register_block(
        CustomDateTimeBlock(),
        signals=(signal.SIGUSR1, signal.SIGUSR2)
    )

    await runner.start()


asyncio.run(main())

Running it and sending pkill -SIGUSR2 i3pyblocks in another terminal result in:

$ i3pyblocks -c config.py
{"version": 1, "click_events": true}
[
[{"name": "CustomDateTimeBlock", "instance": "<random-id>", "full_text": "21:58:27"}],
[{"name": "CustomDateTimeBlock", "instance": "<random-id>", "full_text": "21:58:28"}],
[{"name": "CustomDateTimeBlock", "instance": "<random-id>", "full_text": "09/18/20"}],
[{"name": "CustomDateTimeBlock", "instance": "<random-id>", "full_text": "09/18/20"}],
^C

The same can be applied to mouse clicks overriding the :meth:`~i3pyblocks.blocks.base.Block.click_handler`.

.. seealso::

   For inspiration on how to configure your i3pyblocks, look at `example.py`_
   file. It includes many examples and it is heavily commented.