Expand description
§VNC-RS
§Description
- An async implementation of VNC client side protocol
§Simple example
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(debug_assertions)]
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.finish();
#[cfg(not(debug_assertions))]
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(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 mut canvas = CanvasUtils::new()?;
let mut now = std::time::Instant::now();
loop {
match vnc.poll_event().await {
Ok(Some(e)) => {
let _ = canvas.hande_vnc_event(e);
}
Ok(None) => (),
Err(e) => {
tracing::error!("{}", e.to_string());
break;
}
}
if now.elapsed().as_millis() > 16 {
let _ = canvas.flush();
let _ = vnc.input(X11Event::Refresh).await;
now = std::time::Instant::now();
}
}
canvas.close();
let _ = vnc.close().await;
Ok(())
}
struct CanvasUtils {
window: Window,
video: Vec<u32>,
width: u32,
height: u32,
}
impl CanvasUtils {
fn new() -> Result<Self> {
Ok(Self {
window: Window::new(
"mstsc-rs Remote Desktop in Rust",
800_usize,
600_usize,
WindowOptions::default(),
)
.with_context(|| "Unable to create window".to_string())?,
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
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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.
Re-exports§
pub use client::VncClient;
pub use client::VncConnector;
pub use config::*;
pub use error::*;
pub use event::*;