Skip to main content
Skip to content

Kirigami with Python

Create your first Kirigami application with PySide

Prerequisites

Before getting started, we will need to install Kirigami and PySide on our machine.

logo of Linux operating system ManjaroManjarologo of Linux operating system Arch LinuxArch
sudo pacman -S python-pipx python-pyqt6 pyside6 kirigami flatpak-builder qqc2-desktop-style appstream
logo of Linux operating system openSUSEOpenSUSE
sudo zypper install python3-pipx python3-qt6 python3-pyside6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style AppStream-compose
logo of Linux operating system FedoraFedora
sudo dnf install pipx python3-pyqt6 python3-pyside6 kf6-kirigami-devel flatpak-builder qqc2-desktop-style appstream-compose

If you are on a distribution with old PySide6 or PyQt6 packages, this tutorial works with Building software with distrobox.

Project structure

First we create our project folder (you can use the commands below). We are going to call ours kirigami_python/.

kirigami_python/
├── README.md
├── LICENSE.txt
├── MANIFEST.in                        # To add our QML files
├── pyproject.toml                     # The main file to manage the project
├── org.kde.kirigami_python.desktop
└── src/
    ├── __init__.py                    # To import the src/ directory as a package
    ├── __main__.py                    # To signal app as the entrypoint
    ├── app.py
    └── qml/
        └── Main.qml

The package name is going to be kirigami_python, the "executable" (console script) will be called kirigami_hello, and the entrypoint will be app.

For a more comprehensive project that goes into further detail about this file structure, see Full project in Python + Kirigami.

pyproject.toml

Modern Python applications need only a single TOML file to specify all metadata, package information and dependencies as of PEP 621. The following serves as a good starter for an application and can be extended later.

Most of the contents in this file are boilerplate, and a more complete version of it can be seen in Python with Kirigami: General Structure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "kirigami_python"
version = "0.1"
authors = [
    {name = "Konqi", email = "konqi@example.com"}
]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
    "Intended Audience :: End Users/Desktop",
    "Topic :: Utilities",
    "Programming Language :: Python",
    "Operating System :: POSIX :: Linux",
]

[project.scripts]
kirigami_hello = "kirigami_python.app:main"

[tool.setuptools]
packages = ["kirigami_python"]
package-dir = {kirigami_python = "src"}
include-package-data = true

[tool.setuptools.data-files]
"share/applications" = ["org.kde.kirigami_python.desktop"]

Note the highlighted lines. As mentioned under Project structure, the name of the package is kirigami_python, the name of the executable is kirigami_hello, and the name of the entrypoint is app. In particular, the following should be noted:

  • The project script consists of an entrypoint script that will be generated by setuptools for the application to run, in this case kirigami_hello.
  • The generated project script kirigami_hello runs the main() function in the app.py script in the kirigami_python package.
  • The default package-dir for Python projects is usually the root directory. In this case, this is overridden with the src/ subdirectory so it acts like it is the root directory of the package.
  • The package-dir is why the generated project script does kirigami_python → app instead of kirigami_python → src → app.
  • The package-dir is also why the importlib.resources.files() call in app.py does kirigami_python → qml → Main.qml instead of kirigami_python → src → qml → Main.qml.

See Running directly, as a module, and as a console script for details.

org.kde.kirigami_python.desktop

The primary purpose of Desktop Entry files is to show your app on the application launcher on Linux. Another reason to have them is to have window icons on Wayland, as they are required to tell the compositor "this window goes with this icon".

It must follow a reverse-DNS naming scheme followed by the .desktop extension such as org.kde.kirigami_python.desktop:

1
2
3
4
5
6
7
[Desktop Entry]
Name=Kirigami Tutorial in Python
Exec=kirigami_hello
Icon=kde
Type=Application
Terminal=false
Categories=Utility

MANIFEST.in

This file is simply a declaration of additional source code files that should be present in the package when the application runs. Python by default doesn't include QML files in packages, and they need to be available in order for the application to run.

1
include src/qml/*.qml

src/app.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3

import os
import sys
import signal
from importlib.resources import files

from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QUrl
from PySide6.QtQml import QQmlApplicationEngine

def main():
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    """Needed to close the app with Ctrl+C"""
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    """Needed to get proper KDE style outside of Plasma"""
    if not os.environ.get("QT_QUICK_CONTROLS_STYLE"):
        os.environ["QT_QUICK_CONTROLS_STYLE"] = "org.kde.desktop"

    base_path = files('kirigami_python').joinpath('qml', 'Main.qml')
    url = QUrl(f"{base_path}")
    engine.load(url)

    app.exec()

if __name__ == "__main__":
    main()

Since this is a GUI application, we want the main function to only run when the script is run, not when it's imported, so we need the if __name__ == "__main__" condition at the end of the file. See Running directly, as a module, and as a console script for details.

We create a QGuiApplication and initialize the QML engine, and with QGuiApplication.exec() the application will keep running until closed. Then importlib.resources.files() grabs the path to a file that is present in the package, namely our Main.qml. With that path, we load the QML file into the QML engine as the main entrypoint for the application interface.

src/__init__.py

Create an empty kirigami_python/src/__init__.py file. This file just needs to be present in order to import a directory as a package.

touch __init__.py

src/__main__.py

Create a kirigami_python/src/__main__.py with the following contents:

1
2
3
from . import app

app.main()

This simply adds the contents of the current directory (src/) and imports it as a module named app, then immediately runs the main() function of the application.

src/qml/Main.qml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Includes relevant modules used by the QML
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

// Provides basic features needed for all kirigami applications
Kirigami.ApplicationWindow {
    // Unique identifier to reference this object
    id: root

    width: 400
    height: 300

    // Window title
    title: "Hello World"

    // Set the first page that will be loaded when the app opens
    // This can also be set to an id of a Kirigami.Page
    pageStack.initialPage: Kirigami.Page {
        Controls.Label {
            // Center label horizontally and vertically within parent object
            anchors.centerIn: parent
            text: "Hello World!"
        }
    }
}

Here's where we will be handling our application's frontend.

If you know some Javascript, then much of QML will seem familiar to you (though it does have its own peculiarities). Qt's documentation has an extensive amount of material on this language if you feel like trying something on your own. Over the course of these tutorials we will be focusing much of our attention on our QML code, where we can use Kirigami to get the most out of it.

For now, let's focus on Main.qml. First we import a number of important modules:

  • QtQuick, the standard library used in QML applications.
  • QtQuick Controls, which provides a number of standard controls we can use to make our applications interactive.
  • QtQuick Layouts, which provides tools for placing components within the application window.
  • Kirigami, which provides a number of components suited for creating applications that work across devices of different shapes and sizes.

We then come to our base element, Kirigami.ApplicationWindow, which provides some basic features needed for all Kirigami applications. This is the window that will contain each of our pages, the main sections of our UI.

We then set the window's id property to "root". IDs are useful because they let us uniquely reference a component, even if we have several of the same type.

We also set the window title property to "Hello World".

We then set the first page of our page stack. Most Kirigami applications are organised as a stack of pages, each page containing related components suited to a specific task. For now, we are keeping it simple, and sticking to a single page. pageStack is an initially empty stack of pages provided by Kirigami.ApplicationWindow, and with pageStack.initialPage: Kirigami.Page {...} we set the first page presented upon loading the application to a Kirigami.Page. This page will contain all our content.

Finally, we include in our page a Controls.Label that lets us place text on our page. We use anchors.centerIn: parent to center our label horizontally and vertically within our parent element. In this case, the parent component of our label is Kirigami.Page. The last thing we need to do is set its text: text: "Hello World!".

Running the application

You can run the kirigami_hello console script without needing to install it first:

pipx run --system-site-packages --spec . kirigami_hello

The flag --system-site-packages is needed to make Python have access to the Python packages from your distribution. This is required because Kirigami and PySide need to have been built against the same Qt version to work, which is the case when both of them come from the distribution.

The flag --spec determines the path to the source code or wheel package that has the program, and kirigami_hello is the executable script to be run.

To build and install the Python package, run:

pipx install --force --system-site-packages .

The package will be installed to ~/.local/share/pipx/venvs/kirigami-python, and an executable script will be installed to ~/.local/bin/kirigami_hello.

After this, the application can be launched by running:

kirigami_hello

To run the new QML application in mobile mode, you can use QT_QUICK_CONTROLS_MOBILE=1:

QT_QUICK_CONTROLS_MOBILE=1 kirigami_hello

Voilà! Now you will see your very first Kirigami app appear before your very own eyes.

Screenshot of the generated Kirigami application