5 个不稳定版本

0.5.1 2023年10月10日
0.5.0 2023年9月28日
0.4.1 2023年3月20日
0.3.4 2022年11月2日
0.1.1 2022年10月27日

#20 in WebSocket

Download history 108/week @ 2024-04-28 58/week @ 2024-05-05 66/week @ 2024-05-12 43/week @ 2024-05-19 68/week @ 2024-05-26 86/week @ 2024-06-02 41/week @ 2024-06-09 59/week @ 2024-06-16 46/week @ 2024-06-23 46/week @ 2024-06-30 16/week @ 2024-07-07 62/week @ 2024-07-14 74/week @ 2024-07-21 166/week @ 2024-07-28 307/week @ 2024-08-04 184/week @ 2024-08-11

每月 740 次下载
legba 中使用

MIT/Apache

125KB
2.5K SLoC

vnc-rs

Build API Docs LICENSE LICENSE

描述

异步实现的 VNC 客户端协议

作为协议引擎运行,从前端和 VNC 服务器消耗输入事件,并生成事件以便前端渲染。

可在操作系统和 WASI 上使用。

简单的 X11 客户端使用示例可在此找到 example

简单的 WebAssembly 客户端可在此找到 webvnc

如果您在使用过程中遇到任何问题,欢迎提交 问题pull 请求

为何这样

我最初打算编写一个 VNC 的 wasm 版本,是的,按照 VNC 核心RFC 已经实现了 wasm 版本。

在实现过程中,我发现了一个现有的仓库 whitequark 的 rust vnc。它与操作系统耦合得太紧密(需要多线程和 std::io::TcpStream),无法迁移到 wasm 应用程序。但是,多亏了 whitequark 的工作,我终于找到了如何在客户端进行 VncAuth 的方法。

回顾 whitequark 的 rust vnc我的旧 webvnc,我认为直接将 VNC 协议的解析放入应用程序中并不合适。因此,我分离了引擎部分,结果就是这个 crate。

它旨在成为一个更兼容的 VNC 引擎,可以用于构建 OS 应用程序和 Wasm 应用程序。因此,我尽力最小化依赖项。然而,异步对于 WebSocket 流程是必要的,最终我选择了 tokio 而不是 async_std,这使得它与一些东西不完全兼容。

编码

我仅在win10/Linux ubuntu 20.04上尝试了从紧密的VNC服务器进行视频流传输,以及macOS 12.6上内置的VNC服务器(已开启密码登录)。

Tight编码、Zrle编码和原始编码都运行正常。

但是,在没有明确想法的情况下,当我向VNC服务器发送setClientEncoding(TRLE)时,它响应的是没有编码的原始矩形。因此,Trle编码尚未测试。但是,由于它从zrle例程中分离出来,trle解码例程应该是正确的。

根据RFC,Hextile编码RRE编码都是过时的,所以我没有尝试实现它们。

简单示例

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(())
    }
}

致谢

whitequark的rust vnc.

许可

根据您的要求,许可如下

任选其一。

贡献

除非您明确声明,否则您有意提交并包含在作品中的任何贡献,如Apache-2.0许可中定义,将如上双许可,没有任何附加条款或条件。

依赖关系

~4–12MB
~125K SLoC