Hacking on Qtile
Requirements
Here are Qtile's additional dependencies that may be required for tests:
Dependency |
Ubuntu Package |
Needed for |
---|---|---|
python3-pytest |
Running tests |
|
pre-commit |
Running linters |
|
PyGObject |
python3-gi |
Running tests (test windows) |
xserver-xephyr |
Testing with X11 backend (optional, see below) |
|
mypy |
python3-mypy |
Testing |
imagemagick>=6.8 |
imagemagick |
|
gtk-layer-shell |
libgtk-layer-shell0 |
Testing notification windows in Wayland (optional) |
dbus-launch |
dbus-x11 |
Testing dbus-using widgets (optional) |
notifiy-send |
libnotify-bin |
Testing |
xvfb |
xvfb |
Testing with X11 headless (optional) |
Backends
The test suite can be run using the X11 or Wayland backend, or both. By default, only the X11 backend is used for tests. To test a single backend or both backends, specify as arguments to pytest:
pytest --backend wayland # Test just Wayland backend
pytest --backend x11 --backend wayland # Test both
Testing with the X11 backend requires Xephyr (and xvfb for headless mode) in addition to the core dependencies.
Building cffi module
Qtile ships with a small in-tree pangocairo binding built using cffi,
pangocffi.py
, and also binds to xcursor with cffi. The bindings are not
built at run time and will have to be generated manually when the code is
downloaded or when any changes are made to the cffi library. This can be done
by calling:
./scripts/ffibuild
Setting up the environment
In the root of the project, run ./dev.sh
.
It will create a virtualenv called venv
.
Activate this virtualenv with . venv/bin/activate
.
Deactivate it with the deactivate
command.
Building the documentation
To build the documentation, you will also need to install graphviz.
Go into the docs/
directory and run pip install -r requirements.txt
.
Build the documentation with make html
.
Check the result by opening _build/html/index.html
in your browser.
Note
To speed up local testing, screenshots are not generated each time the documentation is built.
You can enable screenshots by setting the QTILE_BUILD_SCREENSHOTS
environmental
variable at build time e.g. QTILE_BUILD_SCREENSHOTS=1 make html
. You can also
export the variable so it will apply to all local builds export QTILE_BUILD_SCREENSHOTS=1
(but remember to unset it if you want to skip building screenshots).
Development and testing
In practice, the development cycle looks something like this:
make minor code change
run appropriate test:
pytest tests/test_module.py
orpytest -k PATTERN
GOTO 1, until hackage is complete
run entire test suite to make sure you didn't break anything else:
pytest
try to commit, get changes and feedback from the pre-commit hooks
GOTO 5, until your changes actually get committed
Tests and pre-commit hooks will be run by our CI on every pull request as well so you can see whether or not your contribution passes.
Coding style
While not all of our code follows PEP8, we do try to adhere to it where possible. All new code should be PEP8 compliant.
The make lint
command (or pre-commit run -a
) will run our linters and
formatters with our configuration over the whole libqtile to ensure your patch
complies with reasonable formatting constraints. We also request that git commit
messages follow the
standard format.
Logging
Logs are important to us because they are our best way to see what Qtile is doing when something abnormal happens. However, our goal is not to have as many logs as possible, as this hinders readability. What we want are relevant logs.
To decide which log level to use, refer to the following scenarios:
ERROR: a problem affects the behavior of Qtile in a way that is noticeable to the end user, and we can't work around it.
WARNING: a problem causes Qtile to operate in a suboptimal manner.
INFO: the state of Qtile has changed.
DEBUG: information is worth giving to help the developer better understand which branch the process is in.
Be careful not to overuse DEBUG and clutter the logs. No information should be duplicated between two messages.
Also, keep in mind that any other level than DEBUG is aimed at users who don't necessarily have advanced programming knowledge; adapt your message accordingly. If it can't make sense to your grandma, it's probably meant to be a DEBUG message.
Using Xephyr
Qtile has a very extensive test suite, using the Xephyr nested X server. When tests are run, a nested X server with a nested instance of Qtile is fired up, and then tests interact with the Qtile instance through the client API. The fact that we can do this is a great demonstration of just how completely scriptable Qtile is. In fact, Qtile is designed expressly to be scriptable enough to allow unit testing in a nested environment.
The Qtile repo includes a tiny helper script to let you quickly pull up a nested instance of Qtile in Xephyr, using your current configuration. Run it from the top-level of the repository, like this:
./scripts/xephyr
Change the screen size by setting the SCREEN_SIZE
environment variable.
Default: 800x600. Example:
SCREEN_SIZE=1920x1080 ./scripts/xephyr
Change the log level by setting the LOG_LEVEL
environment variable.
Default: INFO. Example:
LOG_LEVEL=DEBUG ./scripts/xephyr
The script will also pass any additional options to Qtile. For example, you can use a specific configuration file like this:
./scripts/xephyr -c ~/.config/qtile/other_config.py
Once the Xephyr window is running and focused, you can enable capturing the keyboard shortcuts by hitting Control+Shift. Hitting them again will disable the capture and let you use your personal keyboard shortcuts again.
You can close the Xephyr window by enabling the capture of keyboard shortcuts
and hit Mod4+Control+Q. Mod4 (or Mod) is usually the Super key (or Windows key).
You can also close the Xephyr window by running qtile cmd-obj -o cmd -f shutdown
in a terminal (from inside the Xephyr window of course).
You don't need to run the Xephyr script in order to run the tests as the test runner will launch its own Xephyr instances.
Second X Session
Some users prefer to test Qtile in a second, completely separate X session:
Just switch to a new tty and run startx
normally to use the ~/.xinitrc
X startup script.
It's likely though that you want to use a different, customized startup script
for testing purposes, for example ~/.config/qtile/xinitrc
. You can do so by
launching X with:
startx ~/.config/qtile/xinitrc
startx
deals with multiple X sessions automatically. If you want to use
xinit
instead, you need to first copy /etc/X11/xinit/xserverrc
to
~/.xserverrc
; when launching it, you have to specify a new session number:
xinit ~/.config/qtile/xinitrc -- :1
Examples of custom X startup scripts are available in qtile-examples.
Debugging in PyCharm
Make sure to have all the requirements installed and your development environment setup.
PyCharm should automatically detect the venv
virtualenv when opening the project.
If you are using another viirtualenv, just instruct PyCharm to use it
in Settings -> Project: qtile -> Project interpreter
.
In the project tree, on the left, right-click on the libqtile
folder,
and click on Mark Directory as -> Sources Root
.
Next, add a Configuration using a Python template with these fields:
Script path:
bin/qtile
, or the absolute path to itParameters:
-c libqtile/resources/default_config.py
, or nothing if you want to use your own config file in~/.config/qtile/config.py
Environment variables:
PYTHONUNBUFFERED=1;DISPLAY=:1
Working directory: the root of the project
Add contents root to PYTHONPATH: yes
Add source root to PYTHONPATH: yes
Then, in a terminal, run:
Xephyr +extension RANDR -screen 1920x1040 :1 -ac &
Note that we used the same display, :1
, in both the terminal command
and the PyCharm configuration environment variables.
Feel free to change the screen size to fit your own screen.
Finally, place your breakpoints in the code and click on Debug
!
Once you finished debugging, you can close the Xephyr window with kill PID
(use the jobs
builtin to get its PID).
Debugging in VSCode
Make sure to have all the requirements installed and your development environment setup.
Open the root of the repo in VSCode. If you have created it, VSCode should
detect the venv
virtualenv, if not, select it.
Create a launch.json file with the following lines.
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Qtile",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/bin/qtile",
"cwd": "${workspaceFolder}",
"args": ["-c", "libqtile/resources/default_config.py"],
"console": "integratedTerminal",
"env": {"PYTHONUNBUFFERED":"1", "DISPLAY":":1"}
}
]
}
Then, in a terminal, run:
Xephyr +extension RANDR -screen 1920x1040 :1 -ac &
Note that we used the same display, :1
, in both the terminal command
and the VSCode configuration environment variables. Then debug
usually
in VSCode. Feel free to change the screen size to fit your own screen.
Resources
Here are a number of resources that may come in handy: