vnc-rs

Description

An async implementation of VNC client side protocol

Simple example

```Rust use anyhow::{Context, Result}; use minifb::{Window, WindowOptions}; use tokio::{self, net::TcpStream}; use tracing::Level; use vnc::{PixelFormat, Rect, VncConnector, VncEvent, X11Event};

[tokio::main]

async fn main() -> Result<()> { // Create tracing subscriber #[cfg(debugassertions)] let subscriber = tracingsubscriber::FmtSubscriber::builder() .withmaxlevel(Level::TRACE) .finish(); #[cfg(not(debugassertions))] let subscriber = tracingsubscriber::FmtSubscriber::builder() .withmaxlevel(Level::INFO) .finish();

tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");

let tcp = TcpStream::connect("127.0.0.1:5900").await?;
let vnc = VncConnector::new(tcp)
    .set_auth_method(async move { Ok("123".to_string()) })
    .add_encoding(vnc::VncEncoding::Tight)
    .add_encoding(vnc::VncEncoding::Zrle)
    .add_encoding(vnc::VncEncoding::CopyRect)
    .add_encoding(vnc::VncEncoding::Raw)
    .allow_shared(true)
    .set_pixel_format(PixelFormat::bgra())
    .build()?
    .try_start()
    .await?
    .finish()?;
let (vnc_event_sender, mut vnc_event_receiver) = tokio::sync::mpsc::channel(100);
let (x11_event_sender, x11_event_receiver) = tokio::sync::mpsc::channel(100);
tokio::spawn(async move { vnc.run(vnc_event_sender, x11_event_receiver).await.unwrap() });

let mut canvas = CanvasUtils::new()?;

while let Some(event) = vnc_event_receiver.recv().await {
    canvas.hande_vnc_event(event)?;
    while let Ok(e) = vnc_event_receiver.try_recv() {
        canvas.hande_vnc_event(e)?;
    }
    canvas.flush()?;
    let _ = x11_event_sender.send(X11Event::Refresh).await;
}
canvas.close();
Ok(())

}

struct CanvasUtils { window: Window, video: Vec, width: u32, height: u32, }

impl CanvasUtils { fn new() -> Result { Ok(Self { window: Window::new( "mstsc-rs Remote Desktop in Rust", 800usize, 600usize, WindowOptions::default(), ) .withcontext(|| "Unable to create window".tostring())?, video: vec![], width: 800, height: 600, }) }

fn init(&mut self, width: u32, height: u32) -> Result<()> {
    let mut window = Window::new(
        "mstsc-rs Remote Desktop in Rust",
        width as usize,
        height as usize,
        WindowOptions::default(),
    )
    .with_context(|| "Unable to create window")?;
    window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
    self.window = window;
    self.width = width;
    self.height = height;
    self.video.resize(height as usize * width as usize, 0);
    Ok(())
}

fn draw(&mut self, rect: Rect, data: Vec<u8>) -> Result<()> {
    // since we set the PixelFormat as bgra
    // the pixels must be sent in [blue, green, red, alpha] in the network order

    let mut s_idx = 0;
    for y in rect.y..rect.y + rect.height {
        let mut d_idx = y as usize * self.width as usize + rect.x as usize;

        for _ in rect.x..rect.x + rect.width {
            self.video[d_idx] =
                u32::from_le_bytes(data[s_idx..s_idx + 4].try_into().unwrap()) & 0x00_ff_ff_ff;
            s_idx += 4;
            d_idx += 1;
        }
    }
    Ok(())
}

fn flush(&mut self) -> Result<()> {
    self.window
        .update_with_buffer(&self.video, self.width as usize, self.height as usize)
        .with_context(|| "Unable to update screen buffer")?;
    Ok(())
}

fn copy(&mut self, dst: Rect, src: Rect) -> Result<()> {
    println!("Copy");
    let mut tmp = vec![0; src.width as usize * src.height as usize];
    let mut tmp_idx = 0;
    for y in 0..src.height as usize {
        let mut s_idx = (src.y as usize + y) * self.width as usize + src.x as usize;
        for _ in 0..src.width {
            tmp[tmp_idx] = self.video[s_idx];
            tmp_idx += 1;
            s_idx += 1;
        }
    }
    tmp_idx = 0;
    for y in 0..src.height as usize {
        let mut d_idx = (dst.y as usize + y) * self.width as usize + dst.x as usize;
        for _ in 0..src.width {
            self.video[d_idx] = tmp[tmp_idx];
            tmp_idx += 1;
            d_idx += 1;
        }
    }
    Ok(())
}

fn close(&self) {}

fn hande_vnc_event(&mut self, event: VncEvent) -> Result<()> {
    match event {
        VncEvent::SetResolution(screen) => {
            tracing::info!("Resize {:?}", screen);
            self.init(screen.width as u32, screen.height as u32)?
        }
        VncEvent::RawImage(rect, data) => {
            self.draw(rect, data)?;
        }
        VncEvent::Bell => {
            tracing::warn!("Bell event got, but ignore it");
        }
        VncEvent::SetPixelFormat(_) => unreachable!(),
        VncEvent::Copy(dst, src) => {
            self.copy(dst, src)?;
        }
        VncEvent::JpegImage(_rect, _data) => {
            tracing::warn!("Jpeg event got, but ignore it");
        }
        VncEvent::SetCursor(rect, data) => {
            if rect.width != 0 {
                self.draw(rect, data)?;
            }
        }
        VncEvent::Text(string) => {
            tracing::info!("Got clipboard message {}", string);
        }
        _ => unreachable!(),
    }
    Ok(())
}

} ```

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.