File: generate-alt-random-writes.py

package info (click to toggle)
foot 1.26.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,692 kB
  • sloc: ansic: 41,627; python: 499; sh: 181; makefile: 18
file content (270 lines) | stat: -rwxr-xr-x 9,783 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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#!/usr/bin/env python3
import argparse
import enum
import fcntl
import random
import signal
import struct
import sys
import termios

from typing import Any


class ColorVariant(enum.IntEnum):
    NONE = enum.auto()
    REGULAR = enum.auto()
    BRIGHT = enum.auto()
    CUBE = enum.auto()
    RGB = enum.auto()


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file')
    parser.add_argument('--cols', type=int)
    parser.add_argument('--rows', type=int)
    parser.add_argument('--colors-regular', action='store_true')
    parser.add_argument('--colors-bright', action='store_true')
    parser.add_argument('--colors-256', action='store_true')
    parser.add_argument('--colors-rgb', action='store_true')
    parser.add_argument('--scroll', action='store_true')
    parser.add_argument('--scroll-region', action='store_true')
    parser.add_argument('--attr-bold', action='store_true')
    parser.add_argument('--attr-italic', action='store_true')
    parser.add_argument('--attr-underline', action='store_true')
    parser.add_argument('--sixel', action='store_true')
    parser.add_argument('--seed', type=int)

    opts = parser.parse_args()
    out = opts.out if opts.out is not None else sys.stdout

    lines: int | None = None
    cols: int | None = None
    width: int | None = None
    height: int | None = None

    if opts.rows is None or opts.cols is None:
        try:
            def dummy(*args: Any) -> None:
                """Need a handler installed for sigwait() to trigger."""
                _ = args
                pass
            signal.signal(signal.SIGWINCH, dummy)

            while True:
                with open('/dev/tty', 'rb') as pty:
                    lines, cols, height, width = struct.unpack(
                        'HHHH',
                        fcntl.ioctl(pty,
                                    termios.TIOCGWINSZ,
                                    struct.pack('HHHH', 0, 0, 0, 0)))

                assert width is not None
                assert height is not None

                if width > 0 and height > 0:
                    break

                # We’re early; the foot window hasn’t been mapped yet. Or,
                # to be more precise, fonts haven’t yet been loaded,
                # meaning it doesn’t have any cell geometry yet.
                signal.sigwait([signal.SIGWINCH])

            signal.signal(signal.SIGWINCH, signal.SIG_DFL)

        except OSError:
            lines = None
            cols = None
            height = None
            width = None

    if opts.rows is not None:
        lines = opts.rows
        assert lines is not None
        height = 15 * lines  # PGO helper binary hardcodes cell height to 15px
    if opts.cols is not None:
        cols = opts.cols
        assert cols is not None
        width = 8 * cols     # PGO help binary hardcodes cell width to 8px

    if lines is None or cols is None or height is None or width is None:
        raise Exception('could not get terminal width/height; use --rows and --cols')

    assert lines > 0, f'{lines}'
    assert cols > 0, f'{cols}'
    assert width > 0, f'{width}'
    assert height > 0, f'{height}'

    # Number of characters to write to screen
    count = 256 * 1024**1

    # Characters to choose from
    alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789 öäå 👨👩🧒👩🏽‍🔬🇸🇪'

    color_variants = ([ColorVariant.NONE] +
                      ([ColorVariant.REGULAR] if opts.colors_regular else []) +
                      ([ColorVariant.BRIGHT] if opts.colors_bright else []) +
                      ([ColorVariant.CUBE] if opts.colors_256 else []) +
                      ([ColorVariant.RGB] if opts.colors_rgb else []))

    # Enter alt screen
    out.write('\033[?1049h')

    # uses system time or /dev/urandom if available if opt.seed == None
    # pin seeding method to make seeding stable across future versions
    random.seed(a=opts.seed, version=2)

    for _ in range(count):
        if opts.scroll and random.randrange(256) == 0:
            out.write('\033[m')

            if opts.scroll_region and random.randrange(256) == 0:
                top = random.randrange(3)
                bottom = random.randrange(3)
                out.write(f'\033[{top};{lines - bottom}r')

            lines_to_scroll = random.randrange(lines - 1)
            rev = random.randrange(2)
            if not rev and random.randrange(2):
                out.write(f'\033[{lines};{cols}H')
                out.write('\n' * lines_to_scroll)
            else:
                out.write(f'\033[{lines_to_scroll + 1}{"T" if rev == 1 else "S"}')
            continue

        # Generate a random location and a random character
        row = random.randrange(lines)
        col = random.randrange(cols)
        c = random.choice(alphabet)

        repeat = random.randrange((cols - col) + 1)
        assert col + repeat <= cols

        color_variant = random.choice(color_variants)

        # Position cursor
        out.write(f'\033[{row + 1};{col + 1}H')

        if color_variant in [ColorVariant.REGULAR, ColorVariant.BRIGHT]:
            do_bg = random.randrange(2)
            base = 40 if do_bg else 30
            base += 60 if color_variant == ColorVariant.BRIGHT else 0

            idx = random.randrange(8)
            out.write(f'\033[{base + idx}m')

        elif color_variant == ColorVariant.CUBE:
            do_bg = random.randrange(2)
            base = 48 if do_bg else 38

            idx = random.randrange(256)
            if random.randrange(2):
                # Old-style
                out.write(f'\033[{base};5;{idx}m')
            else:
                # New-style (sub-parameter based)
                out.write(f'\033[{base}:5:{idx}m')

        elif color_variant == ColorVariant.RGB:
            do_bg = random.randrange(2)
            base = 48 if do_bg else 38

            # use list comprehension in favor of randbytes(n)
            # which is only available for Python >= 3.9
            rgb = [random.randrange(256) for _ in range(3)]

            if random.randrange(2):
                # Old-style
                out.write(f'\033[{base};2;{rgb[0]};{rgb[1]};{rgb[2]}m')
            else:
                # New-style (sub-parameter based)
                out.write(f'\033[{base}:2::{rgb[0]}:{rgb[1]}:{rgb[2]}m')

        if opts.attr_bold and random.randrange(5) == 0:
            out.write('\033[1m')
        if opts.attr_italic and random.randrange(5) == 0:
            out.write('\033[3m')
        if opts.attr_underline and random.randrange(5) == 0:
            out.write('\033[4m')

        out.write(c * repeat)

        do_sgr_reset = random.randrange(2)
        if do_sgr_reset:
            reset_actions = ['\033[m', '\033[39m', '\033[49m']
            out.write(random.choice(reset_actions))

    # Reset colors
    out.write('\033[m\033[r')

    if opts.sixel:
        # The sixel 'alphabet'
        sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

        last_pos: tuple[int, int] | None = None
        last_size: tuple[int, int] = 0, 0

        for _ in range(20):
            if last_pos is not None and random.randrange(2):
                # Overwrite last sixel. I.e. use same position and
                # size as last sixel
                pass
            else:
                # Random origin in upper left quadrant
                last_pos = random.randrange(lines // 2) + 1, random.randrange(cols // 2) + 1
                last_size = random.randrange((height + 1) // 2), random.randrange((width + 1) // 2)

            out.write(f'\033[{last_pos[0]};{last_pos[1]}H')
            six_height, six_width = last_size
            six_rows = (six_height + 5) // 6  # Round up; each sixel is 6 pixels

            # Begin sixel (with P2 set to either 0 or 1 - opaque or transparent)
            sixel_p2 = random.randrange(2)
            out.write(f'\033P;{sixel_p2}q')

            # Sixel size. Without this, sixels will be
            # auto-resized on cell-boundaries.
            out.write(f'"1;1;{six_width};{six_height}')

            # Set up 256 random colors
            for idx in range(256):
                # param 2: 1=HLS, 2=RGB.
                # param 3/4/5: HLS/RGB values in range 0-100
                #              (except 'hue' which is 0..360)
                out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}')

            for row in range(six_rows):
                band_count = random.randrange(4, 33)
                for band in range(band_count):
                    # Choose a random color
                    out.write(f'#{random.randrange(256)}')

                    if random.randrange(2):
                        for col in range(six_width):
                            out.write(f'{random.choice(sixels)}')
                    else:
                        pix_left = six_width
                        while pix_left > 0:
                            repeat_count = random.randrange(1, pix_left + 1)
                            out.write(f'!{repeat_count}{random.choice(sixels)}')
                            pix_left -= repeat_count

                    # Next line
                    if band + 1 < band_count:
                        # Move cursor to beginning of current row
                        out.write('$')
                    elif row + 1 < six_rows:
                        # Newline
                        out.write('-')

            # End sixel
            out.write('\033\\')

    # Leave alt screen
    out.write('\033[?1049l')


if __name__ == '__main__':
    main()