little-kitty 0.0.3

A low-level interface for the Kitty Graphics Protocol
Documentation
use super::super::{command::*, utils::*};

use std::io::*;

// The spec doesn't mention a size limit, but for safety reasons we want one
const MAX_RESPONSE_SIZE: usize = 4096;

const ESC: u8 = b'\x1b';

//
// KittyResponseReader
//

/// Kitty graphics protocol response reader utilities.
pub trait KittyResponseReader {
    /// Read Kitty response.
    fn read_response(&mut self) -> Result<Option<Response>>;

    /// Whether we have a Kitty response followed by a VT100 DA1 query response.
    fn has_kitty_and_device_query_responses(&mut self) -> Result<bool>;
}

impl<ReadT> KittyResponseReader for ReadT
where
    ReadT: Read,
{
    fn read_response(&mut self) -> Result<Option<Response>> {
        if (self.read_byte()? != ESC) || (self.read_byte()? != b'_') || (self.read_byte()? != b'G') {
            return Ok(None);
        }

        let mut response = Vec::default();

        // Keep reading until ESC or at size limit
        let mut size = 0;
        loop {
            match self.read_byte()? {
                ESC => break,

                byte => {
                    if size >= MAX_RESPONSE_SIZE {
                        return Ok(None);
                    }
                    size += 1;
                    response.push(byte);
                }
            }
        }

        if self.read_byte()? != b'\\' {
            return Ok(None);
        }

        Ok(Some(response.as_slice().try_into()?))
    }

    // Checks for either of:
    //
    // * A Kitty response followed by a VT100 device attributes query response = Kitty is supported
    // * Only a VT100 device attributes response = Kitty is not supported
    fn has_kitty_and_device_query_responses(&mut self) -> Result<bool> {
        if self.read_byte()? != ESC {
            return Ok(false);
        }

        match self.read_byte()? {
            // We're in the VT100 response (no Kitty!)
            b'?' => {
                // Keep reading until 'c'
                loop {
                    if self.read_byte()? == b'c' {
                        break;
                    }
                }
                return Ok(false);
            }

            // We're in a Kitty response
            b'_' => {}

            _ => return Ok(false),
        }

        if self.read_byte()? != b'G' {
            return Ok(false);
        }

        // Keep reading until ESC
        loop {
            if self.read_byte()? == ESC {
                break;
            }
        }

        if (self.read_byte()? != b'\\')
            || (self.read_byte()? != ESC)
            || (self.read_byte()? != b'[')
            || (self.read_byte()? != b'?')
        {
            return Ok(false);
        }

        // Keep reading until 'c'
        loop {
            if self.read_byte()? == b'c' {
                break;
            }
        }

        Ok(true)
    }
}