Newer
Older
percord / src / util / util / KittyLogo.ts
import { readFileSync } from "node:fs";

// const originalConsoleLog = console.log;
// console.error =
// 	console.log =
// 	console.debug =
// 		function (message: object, ...optionalParams: object[]) {
// 			KittyLogo.printWithIcon(message + " " + optionalParams.join(" "));
// 		};

export class KittyLogo {
    static isSupported = false;
    private static iconCache: string;

    public static async initialise() {
        this.isSupported = await this.checkSupport();
        if (this.isSupported)
            this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", {
                encoding: "base64",
            });
    }

    public static printLogo(): void {
        const data = readFileSync(__dirname + "/../../../assets/logo.png", {
            encoding: "base64",
        });
        KittyLogo.writeImage({
            base64Data: data,
            width: 86,
            addNewline: true,
        });
    }

    public static printWithIcon(text?: string): void {
        if (text) {
            const lines = text.split("\n");
            for (const line of lines) {
                this.writeIcon();
                process.stdout.write(" " + line + "\n");
            }
        }
    }

    private static writeIcon(): void {
        KittyLogo.writeImage({
            base64Data: this.iconCache,
            width: 2,
            addNewline: false,
        });
    }

    private static checkSupport(): Promise<boolean> {
        if (process.env.FORCE_KITTY) return Promise.resolve(true);
        // Check if we are running in a terminal
        if (!process.stdin.isTTY) return Promise.resolve(false);
        if (!process.stdout.isTTY) return Promise.resolve(false);

        // Check if we are running in a Kitty terminal
        if (process.env.TERM == "xterm-kitty") return Promise.resolve(true);

        // Check if we are running in a common unsupported terminal
        if (process.env.TERM == "xterm") return Promise.resolve(false);
        if (process.env.TERM == "xterm-256color") return Promise.resolve(false);

        return new Promise<boolean>((resolve) => {
            (async () => {
                process.stdin.setEncoding("utf8");
                process.stdin.setRawMode(true);
                let resp = "";
                process.stdin.once("data", function (key) {
                    process.stdin.setRawMode(false);
                    process.stdin.pause();
                    resp = key.toString();
                    if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true);
                    else resolve(false);
                });
                process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c");

                await new Promise((res) => setTimeout(res, 5000));
                resolve(false);
            })();
        });
    }

    private static writeImage(request: KittyImageMetadata) {
        if (!this.isSupported) return;
        let pngData = request.base64Data;

        // Ga=T,q=2,o=z,s=1022,v=181,X=5;
        const chunkSize = 4096;

        //#region Header
        let header = `\x1b_G`; // enable graphics
        header += "a=T"; // action = transmit & display
        header += ",q=2"; // suppress response
        header += ",f=100"; // image format = png
        header += ",t=d"; // transmission = direct
        header += ",x=0"; // current x position
        header += ",y=0"; // current y position
        if (request.width) header += `,c=${request.width}`; // width (columns)
        if (request.height) header += `,r=${request.height}`; // height (rows)
        if (request.widthPixels) header += `,w=${request.widthPixels}`; // width (pixels)
        if (request.heightPixels) header += `,h=${request.heightPixels}`; // height (pixels)
        //#endregion

        while (pngData.length > 0) {
            const dataSize = Math.min(pngData.length, chunkSize);

            process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`);
            process.stdout.write(pngData.slice(0, chunkSize));
            pngData = pngData.slice(chunkSize);
            process.stdout.write("\x1b\\");
        }

        if (request.addNewline) process.stdout.write("\n");
    }
}

export interface KittyImageMetadata {
    base64Data: string;
    width?: number;
    height?: number;
    widthPixels?: number;
    heightPixels?: number;
    addNewline?: boolean;
}