File: conftest.py

package info (click to toggle)
qtile 0.34.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,004 kB
  • sloc: python: 49,959; ansic: 4,371; xml: 324; sh: 260; makefile: 218
file content (205 lines) | stat: -rw-r--r-- 6,719 bytes parent folder | download
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import json
import os
import shutil
from functools import partial
from pathlib import Path

import cairocffi
import pytest

from libqtile.bar import Bar
from libqtile.command.base import expose_command
from libqtile.config import Group, Screen


@pytest.fixture(scope="function")
def vertical(request):
    yield getattr(request, "param", False)


vertical_bar = pytest.mark.parametrize("vertical", [True], indirect=True)


@pytest.fixture(scope="session")
def target():
    folder = Path(__file__).parent / "screenshots"
    docs_folder = (
        Path(__file__).parent
        / ".."
        / ".."
        / ".."
        / "docs"
        / "_static"
        / "screenshots"
        / "widgets"
    )
    log = os.path.join(docs_folder, "shots.json")
    if folder.is_dir():
        shutil.rmtree(folder)
    folder.mkdir()
    key = {}

    def get_file_name(w_name, config):
        nonlocal key

        # Convert config into a string of key=value
        entry = ", ".join(f"{k}={repr(v)}" for k, v in config.items())

        # Check if widget is in the key dict
        if w_name not in key:
            key[w_name] = {}

        # Increment the index number
        indexes = [int(x) for x in key[w_name]]
        index = max(indexes) + 1 if indexes else 1

        # Record the config
        key[w_name][index] = entry

        # Define the target folder and check it exists
        shots_dir = os.path.join(folder, w_name)
        if not os.path.isdir(shots_dir):
            os.mkdir(shots_dir)

        # Returnt the path for the screenshot
        return os.path.join(shots_dir, f"{index}.png")

    yield get_file_name

    # We copy the screenshots from the test folder to the docs folder at the end
    # This prevents pytest deleting the files itself

    # Remove old screenshots
    if os.path.isdir(docs_folder):
        shutil.rmtree(docs_folder)

    # Copy to the docs folder
    shutil.copytree(folder, docs_folder)
    with open(log, "w") as f:
        json.dump(key, f)

    # Clear up the tests folder
    shutil.rmtree(folder)


@pytest.fixture
def screenshot_manager(widget, request, manager_nospawn, minimal_conf_noscreen, target, vertical):
    """
    Create a manager instance for the screenshots. Individual "tests" should only call
    `screenshot_manager.take_screenshot()` but the destination path is also available in
    `screenshot_manager.target`.

    Widgets should create their own `widget` fixture in the relevant file (applying
    monkeypatching etc as necessary).

    Configs can then be passed by parametrizing "screenshot_manager".
    """
    # Partials are used to hide some aspects of the config from being displayed in the
    # docs. We need to split these out into their constituent parts.
    if type(widget) is partial:
        widget_class = widget.func
        widget_config = widget.keywords
    else:
        widget_class = widget
        widget_config = {}

    class ScreenshotWidget(widget_class):
        def __init__(self, *args, **kwargs):
            widget_class.__init__(self, *args, **kwargs)
            # We need the widget's name to be the name of the inherited class
            self.name = widget_class.__name__.lower()

        def _configure(self, bar, screen):
            widget_class._configure(self, bar, screen)

            # By setting `has_mirrors` to True, the drawer will keep a copy of the latest
            # contents in a separate RecordingSurface which we can access for our screenshots.
            self.drawer.has_mirrors = True

        @expose_command()
        def take_screenshot(self, target):
            if not self.configured:
                return

            source = self.drawer.last_surface

            dest = cairocffi.ImageSurface(cairocffi.FORMAT_ARGB32, self.width, self.height)
            with cairocffi.Context(dest) as ctx:
                ctx.set_source_surface(source)
                ctx.paint()

            dest.write_to_png(target)

    class ScreenshotBar(Bar):
        def _configure(self, qtile, screen, **kwargs):
            Bar._configure(self, qtile, screen, **kwargs)

            # By setting `has_mirrors` to True, the drawer will keep a copy of the latest
            # contents in a separate RecordingSurface which we can access for our screenshots.
            self.drawer.has_mirrors = True

        @expose_command()
        def take_screenshot(self, target, x=0, y=0, width=None, height=None):
            """Takes a screenshot of the bar. The area can be selected."""
            if not self._configured:
                return

            if width is None:
                width = self.drawer.width

            if height is None:
                height = self.drawer.height

            # Widgets aren't drawn to the bar's drawer so we first need to render them all to a single surface
            bar_copy = cairocffi.ImageSurface(
                cairocffi.FORMAT_ARGB32, self.drawer.width, self.drawer.height
            )
            with cairocffi.Context(bar_copy) as ctx:
                ctx.set_source_surface(self.drawer.last_surface)
                ctx.paint()

                for i in self.widgets:
                    ctx.set_source_surface(i.drawer.last_surface, i.offsetx, i.offsety)
                    ctx.paint()

            # Then we copy the desired area to our destination surface
            dest = cairocffi.ImageSurface(cairocffi.FORMAT_ARGB32, width, height)
            with cairocffi.Context(dest) as ctx:
                ctx.set_source_surface(bar_copy, x=x, y=y)
                ctx.paint()

            dest.write_to_png(target)

    # Get the widget and config
    config = getattr(request, "param", dict())
    wdgt = ScreenshotWidget(**{**widget_config, **config})
    name = wdgt.name

    # Create a function to generate filename
    def filename():
        return target(name, config)

    # define bars
    position = "left" if vertical else "top"
    bar1 = {position: ScreenshotBar([wdgt], 32)}
    bar2 = {position: ScreenshotBar([], 32)}

    # Add the widget to our config
    minimal_conf_noscreen.groups = [Group(i) for i in "123456789"]
    minimal_conf_noscreen.fake_screens = [
        Screen(**bar1, x=0, y=0, width=300, height=300),
        Screen(**bar2, x=0, y=300, width=300, height=300),
    ]

    manager_nospawn.start(minimal_conf_noscreen)

    # Add some convenience attributes for taking screenshots
    manager_nospawn.target = filename
    ss_widget = manager_nospawn.c.widget[name]
    manager_nospawn.take_screenshot = lambda f=filename: ss_widget.take_screenshot(f())

    yield manager_nospawn


def widget_config(params):
    return pytest.mark.parametrize("screenshot_manager", params, indirect=True)