vncrs 0.1.8

A pure Rust VNC server library for Windows
pub mod hextile;
pub mod raw;
pub mod tight;
pub mod zlib;
pub mod zrle;

use crate::error::Result;
use crate::protocol::{
    ENCODING_HEXTILE, ENCODING_RAW, ENCODING_TIGHT, ENCODING_ZLIB, ENCODING_ZRLE,
    PSEUDO_TIGHT_QUALITY_BASE,
};
use log::info;

pub trait Encoder {
    fn encoding_id(&self) -> i32;
    #[allow(clippy::too_many_arguments)]
    fn encode_rect_into(
        &mut self,
        pixels: &[u8],
        stride: usize,
        x: u16,
        y: u16,
        w: u16,
        h: u16,
        swap_rb: bool,
        out: &mut Vec<u8>,
    ) -> Result<()>;
}

pub struct EncoderSet {
    raw: raw::RawEncoder,
    hextile: hextile::HextileEncoder,
    zlib: zlib::ZlibCompressor,
    zrle: zrle::ZrleEncoder,
    tight: tight::TightEncoder,
    active: i32,
    buf: Vec<u8>,
}

impl Default for EncoderSet {
    fn default() -> Self {
        Self::new()
    }
}

impl EncoderSet {
    pub fn new() -> Self {
        Self {
            raw: raw::RawEncoder,
            hextile: hextile::HextileEncoder::new(),
            zlib: zlib::ZlibCompressor::new(),
            zrle: zrle::ZrleEncoder::new(),
            tight: tight::TightEncoder::new(),
            active: 0,
            buf: Vec::with_capacity(256 * 1024),
        }
    }

    pub fn negotiate(&mut self, client_encodings: &[i32]) {
        // Extract quality pseudo-encodings (-32..=-23: levels 0..9)
        for &enc in client_encodings {
            if (PSEUDO_TIGHT_QUALITY_BASE..=PSEUDO_TIGHT_QUALITY_BASE + 9).contains(&enc) {
                let level = (enc - PSEUDO_TIGHT_QUALITY_BASE) as u8;
                let quality = (level * 10 + 10).clamp(10, 100);
                self.tight.set_quality(quality);
                info!("Client requested Tight quality level {} (JPEG quality {})", level, quality);
            }
        }

        for &enc in client_encodings {
            match enc {
                ENCODING_TIGHT => {
                    info!("Negotiated encoding: Tight (ID 7)");
                    self.active = enc;
                    return;
                }
                ENCODING_ZRLE => {
                    info!("Negotiated encoding: ZRLE (ID 16)");
                    self.active = enc;
                    return;
                }
                ENCODING_HEXTILE => {
                    info!("Negotiated encoding: Hextile (ID 5)");
                    self.active = enc;
                    return;
                }
                ENCODING_ZLIB => {
                    info!("Negotiated encoding: Zlib (ID 6)");
                    self.active = enc;
                    return;
                }
                ENCODING_RAW => {
                    info!("Negotiated encoding: Raw (ID 0)");
                    self.active = enc;
                    return;
                }
                _ => continue,
            }
        }
        info!("Negotiated encoding: Raw (fallback)");
        self.active = 0;
    }

    pub fn encoding_id(&self) -> i32 {
        self.active
    }

    #[allow(clippy::too_many_arguments)]
    pub fn encode_rect(
        &mut self,
        pixels: &[u8],
        stride: usize,
        x: u16,
        y: u16,
        w: u16,
        h: u16,
        swap_rb: bool,
    ) -> Result<&[u8]> {
        self.buf.clear();
        match self.active {
            ENCODING_TIGHT => self
                .tight
                .encode_rect_into(pixels, stride, x, y, w, h, swap_rb, &mut self.buf)?,
            ENCODING_ZRLE => self
                .zrle
                .encode_rect_into(pixels, stride, x, y, w, h, swap_rb, &mut self.buf)?,
            ENCODING_HEXTILE => {
                self.hextile
                    .encode_rect_into(pixels, stride, x, y, w, h, swap_rb, &mut self.buf)?
            }
            ENCODING_ZLIB => self
                .zlib
                .encode_rect_into(pixels, stride, x, y, w, h, swap_rb, &mut self.buf)?,
            _ => self
                .raw
                .encode_rect_into(pixels, stride, x, y, w, h, swap_rb, &mut self.buf)?,
        }
        Ok(&self.buf)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_negotiate_tight() {
        let mut encoders = EncoderSet::new();
        // Client sends Tight, ZRLE, Raw and Quality level 8 (-24)
        encoders.negotiate(&[-24, ENCODING_TIGHT, ENCODING_ZRLE, ENCODING_RAW]);
        assert_eq!(encoders.encoding_id(), ENCODING_TIGHT);
    }

    #[test]
    fn test_negotiate_zrle_when_tight_absent() {
        let mut encoders = EncoderSet::new();
        encoders.negotiate(&[ENCODING_ZRLE, ENCODING_RAW]);
        assert_eq!(encoders.encoding_id(), ENCODING_ZRLE);
    }

    #[test]
    fn test_negotiate_fallback_raw() {
        let mut encoders = EncoderSet::new();
        encoders.negotiate(&[999, 888]);
        assert_eq!(encoders.encoding_id(), ENCODING_RAW);
    }
}