aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/synthetic/Kitty.zig
blob: c2c649d716fcc33258c7a8c5570e1952f9b61429 (plain) (blame)
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
//! Generates random Kitty graphics protocol APC sequences.
//!
//! The payload is random base64, NOT a valid image: decoding it as
//! PNG will fail. This corpus is meant for benchmarking the stream,
//! APC, and Kitty command parsing paths, which never decode the
//! image data. It does not exercise successful image loading.
const Kitty = @This();

const std = @import("std");
const assert = std.debug.assert;
const Generator = @import("Generator.zig");
const Bytes = @import("Bytes.zig");

/// Random number generator.
rand: std.Random,

/// The base64 payload length for each generated command. This is
/// rounded down to a multiple of four so the payload is always valid
/// base64 without requiring padding. Kitty clients typically chunk
/// payloads at 4096 bytes.
data_len: usize = 4096,

fn checkBase64Alphabet(c: u8) bool {
    return switch (c) {
        'A'...'Z', 'a'...'z', '0'...'9', '+', '/' => true,
        else => false,
    };
}

/// The base64 alphabet, without the padding character.
pub const base64_alphabet = Bytes.generateAlphabet(checkBase64Alphabet);

pub fn generator(self: *Kitty) Generator {
    return .init(self, next);
}

const prefix = "\x1b_G";
const st = "\x1b\\";

/// Get the next Kitty graphics APC sequence: a well-formed transmit
/// command with a random base64 payload (not a valid image; see the
/// module comment), including the APC prefix and the ST terminator.
pub fn next(
    self: *Kitty,
    writer: *std.Io.Writer,
    max_len: usize,
) Generator.Error!void {
    var control_buf: [64]u8 = undefined;
    const control = std.fmt.bufPrint(
        &control_buf,
        "a=t,f=100,i={d};",
        .{self.rand.intRangeAtMost(u32, 1, 1_000_000)},
    ) catch unreachable;

    const overhead = prefix.len + control.len + st.len;
    assert(max_len > overhead);

    const avail = @min(self.data_len, max_len - overhead);
    const payload_len = avail - (avail % 4);

    try writer.writeAll(prefix);
    try writer.writeAll(control);
    if (payload_len > 0) {
        const bytes: Bytes = .{
            .rand = self.rand,
            .alphabet = base64_alphabet,
            .min_len = payload_len,
            .max_len = payload_len,
        };
        _ = try bytes.write(writer);
    }
    try writer.writeAll(st);
}

test "kitty" {
    const testing = std.testing;
    var prng = std.Random.DefaultPrng.init(0);

    var buf: [8192]u8 = undefined;
    var writer: std.Io.Writer = .fixed(&buf);
    var v: Kitty = .{ .rand = prng.random() };
    const gen = v.generator();
    try gen.next(&writer, buf.len);

    const data = writer.buffered();
    try testing.expect(std.mem.startsWith(u8, data, "\x1b_Ga=t,f=100,i="));
    try testing.expect(std.mem.endsWith(u8, data, "\x1b\\"));
}