├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── example ├── .gitignore ├── Cargo.toml └── src │ └── main.rs └── src ├── client ├── auth.rs ├── connection.rs ├── connector.rs ├── messages.rs ├── mod.rs └── security │ ├── des.rs │ └── mod.rs ├── codec ├── cursor.rs ├── mod.rs ├── raw.rs ├── tight.rs ├── trle.rs ├── zlib.rs └── zrle.rs ├── config.rs ├── error.rs ├── event.rs └── lib.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | cargo_fmt: 14 | name: Check cargo formatting 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Run cargo fmt 19 | run: cargo fmt --all -- --check 20 | 21 | cargo_clippy: 22 | name: Check cargo clippy 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Install Clippy 27 | run: rustup component add clippy 28 | - name: Clippy (no features enabled) 29 | run: cargo clippy -- -D warnings 30 | - name: Clippy (all features enabled) 31 | run: cargo clippy --all-features -- -D warnings 32 | 33 | build-linux: 34 | name: Build check on linux 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: Build Linux (no features enabled) 39 | run: cargo build --verbose 40 | - name: Build Linux (all features enabled) 41 | run: cargo build --verbose --all-features 42 | 43 | build-wasm32: 44 | name: Build check for wasm32 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | - name: Add wasm32 49 | run: rustup target add wasm32-unknown-unknown 50 | - name: Build wasm32 (no features enabled) 51 | run: cargo build --target wasm32-unknown-unknown --verbose 52 | - name: Build wasm32 (all features enabled) 53 | run: cargo build --target wasm32-unknown-unknown --verbose --all-features 54 | 55 | build-windows: 56 | name: Build check on windows 57 | runs-on: windows-2019 58 | steps: 59 | - uses: actions/checkout@v3 60 | - name: Build Windows (no features enabled) 61 | run: cargo build --verbose 62 | - name: Build Windows (all features enabled) 63 | run: cargo build --verbose --all-features 64 | 65 | cargo-test: 66 | name: Check Cargo test 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v3 70 | - name: Test 71 | run: cargo test --all-features 72 | - name: Doc test 73 | run: cargo test --doc --all-features -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vnc-rs" 3 | version = "0.5.2" 4 | edition = "2021" 5 | authors = ["Jovi Hsu ", "Petr Beneš "] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/HsuJv/vnc-rs" 9 | homepage = "https://github.com/HsuJv/vnc-rs" 10 | documentation = "https://docs.rs/vnc-rs" 11 | description = "An async implementation of VNC client side protocol" 12 | keywords = ["vnc"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | name = "vnc" 18 | path = "src/lib.rs" 19 | 20 | [dependencies] 21 | #error 22 | thiserror = "^1" 23 | flate2 = "^1" 24 | 25 | #log 26 | tracing = { version = "^0.1", features = ["log"] } 27 | 28 | # async 29 | async_io_stream = "0.3" 30 | futures = "0.3" 31 | tokio-util = { version = "0.7", features = ["compat"] } 32 | tokio-stream = "0.1" 33 | 34 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 35 | tokio = { version = "^1", features = ["full"] } 36 | 37 | [target.'cfg(target_arch = "wasm32")'.dependencies] 38 | wasm-bindgen-futures = "0.4" 39 | tokio = { version = "^1", features = [ 40 | "sync", 41 | "macros", 42 | "io-util", 43 | "rt", 44 | "time" 45 | ]} 46 | 47 | [dev-dependencies] 48 | anyhow = "^1" 49 | tracing-subscriber = { version = "^0.3" } 50 | minifb = "0.25.0" 51 | 52 | 53 | [profile.release] 54 | # Tell `rustc` to optimize for small code size. 55 | opt-level = "s" 56 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jovi Hsu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vnc-rs 2 | 3 | [![Build](https://github.com/HsuJv/vnc-rs/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/HsuJv/vnc-rs/actions/workflows/build.yml) 4 | [![API Docs](https://docs.rs/vnc-rs/badge.svg)](https://docs.rs/vnc-rs/latest/vnc) 5 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) 6 | [![LICENSE](https://img.shields.io/badge/license-Apache-blue.svg)](LICENSE-APACHE) 7 | 8 | ## Description 9 | 10 | An async implementation of VNC client side protocol 11 | 12 | Worked as a protocol engine which consumes input event from the frontend and the vnc server and generate event to let the frontend render. 13 | 14 | Can be used on both the OS and WASI. 15 | 16 | A simple X11 client usage can be found at [example](https://github.com/HsuJv/vnc-rs/blob/main/example/src/main.rs) 17 | 18 | A simple web assembly client can be found at [webvnc](https://github.com/HsuJv/webgateway/tree/main/webvnc/src) 19 | 20 | If you encounter any problems in use, Any [issues](https://github.com/HsuJv/vnc-rs/issues) or [pull requests](https://github.com/HsuJv/vnc-rs/pulls) are welcome. 21 | 22 | ## Why this 23 | 24 | I initially intended to write a wasm version of vnc, and yes, a wasm version has been implemented as per the [VNC Core RFC](https://www.rfc-editor.org/rfc/rfc6143.html) 25 | 26 | During the implementation I found an existing respository [whitequark's rust vnc](https://github.com/whitequark/rust-vnc). Which is too coupled to the OS (requring multi-thread and `std::io::TcpStream`) to migrate to a wasm application. But thanks to whitequark's work, I finally worked out how to do VncAuth on the client side. 27 | 28 | Looking back [whitequark's rust vnc](https://github.com/whitequark/rust-vnc) and [my old webvnc](https://github.com/HsuJv/webgateway/tree/a031a9d0472677cb17cc269abdd1cbc7349582bc/webvnc), I didn't think it would be appropriate to put the parsing of the vnc protocol directly into the application. So I separated the engine part and the result is this crate. 29 | 30 | It is intended to be a more compatible vnc engine that can be built for both OS applications and Wasm applications. So I did my best to minimise dependencies. However, asynchrony is necessary for websocket processes and in the end I chose `tokio` over `async_std`, which makes it a bit incompatible. 31 | 32 | ## Encodings 33 | I've only tried video streaming from tight vnc server on win10/ubuntu 20.04 and the built-in vnc server on macos 12.6 (with password login turned on) 34 | 35 | Tight encoding, Zrle encoding & Raw encoding all work fine. 36 | 37 | But without any idea, when I send setClientEncoding(TRLE) to the vnc server it response with raw rectangles without any encoding. So Trle encoding is not tested. But the trle decoding routine shall be right since it was split from zrle routine 38 | 39 | According to the RFC, the [Hextile Encoding](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.7.4) and [RRE Encoding](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.7.3) are both obsolescent, so I didn't try to implement them. 40 | 41 | ## Simple example 42 | 43 | ```Rust 44 | use anyhow::{Context, Result}; 45 | use minifb::{Window, WindowOptions}; 46 | use tokio::{self, net::TcpStream}; 47 | use tracing::Level; 48 | use vnc::{PixelFormat, Rect, VncConnector, VncEvent, X11Event}; 49 | 50 | #[tokio::main] 51 | async fn main() -> Result<()> { 52 | // Create tracing subscriber 53 | #[cfg(debug_assertions)] 54 | let subscriber = tracing_subscriber::FmtSubscriber::builder() 55 | .with_max_level(Level::TRACE) 56 | .finish(); 57 | #[cfg(not(debug_assertions))] 58 | let subscriber = tracing_subscriber::FmtSubscriber::builder() 59 | .with_max_level(Level::INFO) 60 | .finish(); 61 | 62 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 63 | 64 | let tcp = TcpStream::connect("127.0.0.1:5900").await?; 65 | let vnc = VncConnector::new(tcp) 66 | .set_auth_method(async move { Ok("123".to_string()) }) 67 | .add_encoding(vnc::VncEncoding::Tight) 68 | .add_encoding(vnc::VncEncoding::Zrle) 69 | .add_encoding(vnc::VncEncoding::CopyRect) 70 | .add_encoding(vnc::VncEncoding::Raw) 71 | .allow_shared(true) 72 | .set_pixel_format(PixelFormat::bgra()) 73 | .build()? 74 | .try_start() 75 | .await? 76 | .finish()?; 77 | 78 | let mut canvas = CanvasUtils::new()?; 79 | 80 | let mut now = std::time::Instant::now(); 81 | loop { 82 | match vnc.poll_event().await { 83 | Ok(Some(e)) => { 84 | let _ = canvas.hande_vnc_event(e); 85 | } 86 | Ok(None) => (), 87 | Err(e) => { 88 | tracing::error!("{}", e.to_string()); 89 | break; 90 | } 91 | } 92 | if now.elapsed().as_millis() > 16 { 93 | let _ = canvas.flush(); 94 | let _ = vnc.input(X11Event::Refresh).await; 95 | now = std::time::Instant::now(); 96 | } 97 | } 98 | canvas.close(); 99 | let _ = vnc.close().await; 100 | Ok(()) 101 | } 102 | 103 | struct CanvasUtils { 104 | window: Window, 105 | video: Vec, 106 | width: u32, 107 | height: u32, 108 | } 109 | 110 | impl CanvasUtils { 111 | fn new() -> Result { 112 | Ok(Self { 113 | window: Window::new( 114 | "mstsc-rs Remote Desktop in Rust", 115 | 800_usize, 116 | 600_usize, 117 | WindowOptions::default(), 118 | ) 119 | .with_context(|| "Unable to create window".to_string())?, 120 | video: vec![], 121 | width: 800, 122 | height: 600, 123 | }) 124 | } 125 | 126 | fn init(&mut self, width: u32, height: u32) -> Result<()> { 127 | let mut window = Window::new( 128 | "mstsc-rs Remote Desktop in Rust", 129 | width as usize, 130 | height as usize, 131 | WindowOptions::default(), 132 | ) 133 | .with_context(|| "Unable to create window")?; 134 | window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); 135 | self.window = window; 136 | self.width = width; 137 | self.height = height; 138 | self.video.resize(height as usize * width as usize, 0); 139 | Ok(()) 140 | } 141 | 142 | fn draw(&mut self, rect: Rect, data: Vec) -> Result<()> { 143 | // since we set the PixelFormat as bgra 144 | // the pixels must be sent in [blue, green, red, alpha] in the network order 145 | 146 | let mut s_idx = 0; 147 | for y in rect.y..rect.y + rect.height { 148 | let mut d_idx = y as usize * self.width as usize + rect.x as usize; 149 | 150 | for _ in rect.x..rect.x + rect.width { 151 | self.video[d_idx] = 152 | u32::from_le_bytes(data[s_idx..s_idx + 4].try_into().unwrap()) & 0x00_ff_ff_ff; 153 | s_idx += 4; 154 | d_idx += 1; 155 | } 156 | } 157 | Ok(()) 158 | } 159 | 160 | fn flush(&mut self) -> Result<()> { 161 | self.window 162 | .update_with_buffer(&self.video, self.width as usize, self.height as usize) 163 | .with_context(|| "Unable to update screen buffer")?; 164 | Ok(()) 165 | } 166 | 167 | fn copy(&mut self, dst: Rect, src: Rect) -> Result<()> { 168 | println!("Copy"); 169 | let mut tmp = vec![0; src.width as usize * src.height as usize]; 170 | let mut tmp_idx = 0; 171 | for y in 0..src.height as usize { 172 | let mut s_idx = (src.y as usize + y) * self.width as usize + src.x as usize; 173 | for _ in 0..src.width { 174 | tmp[tmp_idx] = self.video[s_idx]; 175 | tmp_idx += 1; 176 | s_idx += 1; 177 | } 178 | } 179 | tmp_idx = 0; 180 | for y in 0..src.height as usize { 181 | let mut d_idx = (dst.y as usize + y) * self.width as usize + dst.x as usize; 182 | for _ in 0..src.width { 183 | self.video[d_idx] = tmp[tmp_idx]; 184 | tmp_idx += 1; 185 | d_idx += 1; 186 | } 187 | } 188 | Ok(()) 189 | } 190 | 191 | fn close(&self) {} 192 | 193 | fn hande_vnc_event(&mut self, event: VncEvent) -> Result<()> { 194 | match event { 195 | VncEvent::SetResolution(screen) => { 196 | tracing::info!("Resize {:?}", screen); 197 | self.init(screen.width as u32, screen.height as u32)? 198 | } 199 | VncEvent::RawImage(rect, data) => { 200 | self.draw(rect, data)?; 201 | } 202 | VncEvent::Bell => { 203 | tracing::warn!("Bell event got, but ignore it"); 204 | } 205 | VncEvent::SetPixelFormat(_) => unreachable!(), 206 | VncEvent::Copy(dst, src) => { 207 | self.copy(dst, src)?; 208 | } 209 | VncEvent::JpegImage(_rect, _data) => { 210 | tracing::warn!("Jpeg event got, but ignore it"); 211 | } 212 | VncEvent::SetCursor(rect, data) => { 213 | if rect.width != 0 { 214 | self.draw(rect, data)?; 215 | } 216 | } 217 | VncEvent::Text(string) => { 218 | tracing::info!("Got clipboard message {}", string); 219 | } 220 | _ => unreachable!(), 221 | } 222 | Ok(()) 223 | } 224 | } 225 | ``` 226 | 227 | ## Acknowledgements 228 | [whitequark's rust vnc](https://github.com/whitequark/rust-vnc). 229 | 230 | ## License 231 | 232 | Licensed under either of 233 | 234 | * Apache License, Version 2.0 235 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 236 | * MIT license 237 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 238 | 239 | at your option. 240 | 241 | ## Contribution 242 | 243 | Unless you explicitly state otherwise, any contribution intentionally submitted 244 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 245 | dual licensed as above, without any additional terms or conditions. 246 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vnc-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Jovi Hsu "] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/HsuJv/vnc-rs" 9 | homepage = "https://github.com/HsuJv/vnc-rs" 10 | documentation = "https://docs.rs/vnc-rs" 11 | description = "An async implementation of VNC client side protocol" 12 | keywords = ["vnc"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [[bin]] 17 | name = "vnc-rs" 18 | 19 | [dependencies] 20 | vnc-rs = { path="../" } 21 | #error 22 | thiserror = "^1.0" 23 | anyhow = "^1.0" 24 | 25 | #log 26 | tracing = { version = "^0.1", features = ["log"] } 27 | tracing-subscriber = { version = "^0.3" } 28 | 29 | # async 30 | tokio = { version = "^1", features = ["full"] } 31 | 32 | # x11 33 | minifb = "0.23.0" 34 | 35 | 36 | [profile.release] 37 | # Tell `rustc` to optimize for small code size. 38 | opt-level = "s" 39 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use minifb::{Window, WindowOptions}; 3 | use tokio::{self, net::TcpStream}; 4 | use tracing::Level; 5 | use vnc::{PixelFormat, Rect, VncConnector, VncEvent, X11Event}; 6 | 7 | struct CanvasUtils { 8 | window: Window, 9 | video: Vec, 10 | width: u32, 11 | height: u32, 12 | } 13 | 14 | impl CanvasUtils { 15 | fn new() -> Result { 16 | Ok(Self { 17 | window: Window::new( 18 | "mstsc-rs Remote Desktop in Rust", 19 | 800_usize, 20 | 600_usize, 21 | WindowOptions::default(), 22 | ) 23 | .with_context(|| "Unable to create window".to_string())?, 24 | video: vec![], 25 | width: 800, 26 | height: 600, 27 | }) 28 | } 29 | 30 | fn init(&mut self, width: u32, height: u32) -> Result<()> { 31 | let mut window = Window::new( 32 | "mstsc-rs Remote Desktop in Rust", 33 | width as usize, 34 | height as usize, 35 | WindowOptions::default(), 36 | ) 37 | .with_context(|| "Unable to create window")?; 38 | window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); 39 | self.window = window; 40 | self.width = width; 41 | self.height = height; 42 | self.video.resize(height as usize * width as usize, 0); 43 | Ok(()) 44 | } 45 | 46 | fn draw(&mut self, rect: Rect, data: Vec) -> Result<()> { 47 | // since we set the PixelFormat as bgra 48 | // the pixels must be sent in [blue, green, red, alpha] in the network order 49 | 50 | let mut s_idx = 0; 51 | for y in rect.y..rect.y + rect.height { 52 | let mut d_idx = y as usize * self.width as usize + rect.x as usize; 53 | 54 | for _ in rect.x..rect.x + rect.width { 55 | self.video[d_idx] = 56 | u32::from_le_bytes(data[s_idx..s_idx + 4].try_into().unwrap()) & 0x00_ff_ff_ff; 57 | s_idx += 4; 58 | d_idx += 1; 59 | } 60 | } 61 | Ok(()) 62 | } 63 | 64 | fn flush(&mut self) -> Result<()> { 65 | self.window 66 | .update_with_buffer(&self.video, self.width as usize, self.height as usize) 67 | .with_context(|| "Unable to update screen buffer")?; 68 | Ok(()) 69 | } 70 | 71 | fn copy(&mut self, dst: Rect, src: Rect) -> Result<()> { 72 | println!("Copy"); 73 | let mut tmp = vec![0; src.width as usize * src.height as usize]; 74 | let mut tmp_idx = 0; 75 | for y in 0..src.height as usize { 76 | let mut s_idx = (src.y as usize + y) * self.width as usize + src.x as usize; 77 | for _ in 0..src.width { 78 | tmp[tmp_idx] = self.video[s_idx]; 79 | tmp_idx += 1; 80 | s_idx += 1; 81 | } 82 | } 83 | tmp_idx = 0; 84 | for y in 0..src.height as usize { 85 | let mut d_idx = (dst.y as usize + y) * self.width as usize + dst.x as usize; 86 | for _ in 0..src.width { 87 | self.video[d_idx] = tmp[tmp_idx]; 88 | tmp_idx += 1; 89 | d_idx += 1; 90 | } 91 | } 92 | Ok(()) 93 | } 94 | 95 | fn close(&self) {} 96 | 97 | fn hande_vnc_event(&mut self, event: VncEvent) -> Result<()> { 98 | match event { 99 | VncEvent::SetResolution(screen) => { 100 | tracing::info!("Resize {:?}", screen); 101 | self.init(screen.width as u32, screen.height as u32)? 102 | } 103 | VncEvent::RawImage(rect, data) => { 104 | self.draw(rect, data)?; 105 | } 106 | VncEvent::Bell => { 107 | tracing::warn!("Bell event got, but ignore it"); 108 | } 109 | VncEvent::SetPixelFormat(_) => unreachable!(), 110 | VncEvent::Copy(dst, src) => { 111 | self.copy(dst, src)?; 112 | } 113 | VncEvent::JpegImage(_rect, _data) => { 114 | tracing::warn!("Jpeg event got, but ignore it"); 115 | } 116 | VncEvent::SetCursor(rect, data) => { 117 | if rect.width != 0 { 118 | self.draw(rect, data)?; 119 | } 120 | } 121 | VncEvent::Text(string) => { 122 | tracing::info!("Got clipboard message {}", string); 123 | } 124 | _ => tracing::error!("{:?}", event), 125 | } 126 | Ok(()) 127 | } 128 | } 129 | 130 | #[tokio::main] 131 | async fn main() -> Result<()> { 132 | // Create tracing subscriber 133 | #[cfg(debug_assertions)] 134 | let subscriber = tracing_subscriber::fmt() 135 | .pretty() 136 | .with_max_level(Level::TRACE) 137 | .finish(); 138 | #[cfg(not(debug_assertions))] 139 | let subscriber = tracing_subscriber::fmt() 140 | .pretty() 141 | .with_max_level(Level::INFO) 142 | .finish(); 143 | 144 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 145 | 146 | let tcp = TcpStream::connect("127.0.0.1:5900").await?; 147 | let vnc = VncConnector::new(tcp) 148 | .set_auth_method(async move { Ok("123".to_string()) }) 149 | .add_encoding(vnc::VncEncoding::Tight) 150 | .add_encoding(vnc::VncEncoding::Zrle) 151 | .add_encoding(vnc::VncEncoding::CopyRect) 152 | .add_encoding(vnc::VncEncoding::Raw) 153 | .allow_shared(true) 154 | .set_pixel_format(PixelFormat::bgra()) 155 | .build()? 156 | .try_start() 157 | .await? 158 | .finish()?; 159 | 160 | let mut canvas = CanvasUtils::new()?; 161 | 162 | let mut now = std::time::Instant::now(); 163 | loop { 164 | match vnc.poll_event().await { 165 | Ok(Some(e)) => { 166 | let _ = canvas.hande_vnc_event(e); 167 | } 168 | Ok(None) => (), 169 | Err(e) => { 170 | tracing::error!("{}", e.to_string()); 171 | break; 172 | } 173 | } 174 | if now.elapsed().as_millis() > 16 { 175 | let _ = canvas.flush(); 176 | let _ = vnc.input(X11Event::Refresh).await; 177 | now = std::time::Instant::now(); 178 | } 179 | } 180 | canvas.close(); 181 | let _ = vnc.close().await; 182 | Ok(()) 183 | } 184 | -------------------------------------------------------------------------------- /src/client/auth.rs: -------------------------------------------------------------------------------- 1 | use super::security; 2 | use crate::{VncError, VncVersion}; 3 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | #[repr(u8)] 8 | pub(super) enum SecurityType { 9 | Invalid = 0, 10 | None = 1, 11 | VncAuth = 2, 12 | RA2 = 5, 13 | RA2ne = 6, 14 | Tight = 16, 15 | Ultra = 17, 16 | Tls = 18, 17 | VeNCrypt = 19, 18 | GtkVncSasl = 20, 19 | Md5Hash = 21, 20 | ColinDeanXvp = 22, 21 | } 22 | 23 | impl TryFrom for SecurityType { 24 | type Error = VncError; 25 | fn try_from(num: u8) -> Result { 26 | match num { 27 | 0 | 1 | 2 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 => { 28 | Ok(unsafe { std::mem::transmute::(num) }) 29 | } 30 | invalid => Err(VncError::InvalidSecurityTyep(invalid)), 31 | } 32 | } 33 | } 34 | 35 | impl From for u8 { 36 | fn from(e: SecurityType) -> Self { 37 | e as u8 38 | } 39 | } 40 | 41 | impl SecurityType { 42 | pub(super) async fn read(reader: &mut S, version: &VncVersion) -> Result, VncError> 43 | where 44 | S: AsyncRead + Unpin, 45 | { 46 | match version { 47 | VncVersion::RFB33 => { 48 | let security_type = reader.read_u32().await?; 49 | let security_type = (security_type as u8).try_into()?; 50 | if let SecurityType::Invalid = security_type { 51 | let _ = reader.read_u32().await?; 52 | let mut err_msg = String::new(); 53 | reader.read_to_string(&mut err_msg).await?; 54 | return Err(VncError::General(err_msg)); 55 | } 56 | Ok(vec![security_type]) 57 | } 58 | _ => { 59 | // +--------------------------+-------------+--------------------------+ 60 | // | No. of bytes | Type | Description | 61 | // | | [Value] | | 62 | // +--------------------------+-------------+--------------------------+ 63 | // | 1 | U8 | number-of-security-types | 64 | // | number-of-security-types | U8 array | security-types | 65 | // +--------------------------+-------------+--------------------------+ 66 | let num = reader.read_u8().await?; 67 | 68 | if num == 0 { 69 | let _ = reader.read_u32().await?; 70 | let mut err_msg = String::new(); 71 | reader.read_to_string(&mut err_msg).await?; 72 | return Err(VncError::General(err_msg)); 73 | } 74 | let mut sec_types = vec![]; 75 | for _ in 0..num { 76 | sec_types.push(reader.read_u8().await?.try_into()?); 77 | } 78 | tracing::trace!("Server supported security type: {:?}", sec_types); 79 | Ok(sec_types) 80 | } 81 | } 82 | } 83 | 84 | pub(super) async fn write(&self, writer: &mut S) -> Result<(), VncError> 85 | where 86 | S: AsyncWrite + Unpin, 87 | { 88 | writer.write_all(&[(*self).into()]).await?; 89 | Ok(()) 90 | } 91 | } 92 | 93 | #[allow(dead_code)] 94 | #[repr(u32)] 95 | pub(super) enum AuthResult { 96 | Ok = 0, 97 | Failed = 1, 98 | } 99 | 100 | impl From for AuthResult { 101 | fn from(num: u32) -> Self { 102 | unsafe { std::mem::transmute(num) } 103 | } 104 | } 105 | 106 | impl From for u32 { 107 | fn from(e: AuthResult) -> Self { 108 | e as u32 109 | } 110 | } 111 | 112 | pub(super) struct AuthHelper { 113 | challenge: [u8; 16], 114 | key: [u8; 8], 115 | } 116 | 117 | impl AuthHelper { 118 | pub(super) async fn read(reader: &mut S, credential: &str) -> Result 119 | where 120 | S: AsyncRead + Unpin, 121 | { 122 | let mut challenge = [0; 16]; 123 | reader.read_exact(&mut challenge).await?; 124 | 125 | let credential_len = credential.len(); 126 | let mut key = [0u8; 8]; 127 | for (i, key_i) in key.iter_mut().enumerate() { 128 | let c = if i < credential_len { 129 | credential.as_bytes()[i] 130 | } else { 131 | 0 132 | }; 133 | let mut cs = 0u8; 134 | for j in 0..8 { 135 | cs |= ((c >> j) & 1) << (7 - j) 136 | } 137 | *key_i = cs; 138 | } 139 | 140 | Ok(Self { challenge, key }) 141 | } 142 | 143 | pub(super) async fn write(&self, writer: &mut S) -> Result<(), VncError> 144 | where 145 | S: AsyncWrite + Unpin, 146 | { 147 | let encrypted = security::des::encrypt(&self.challenge, &self.key); 148 | writer.write_all(&encrypted).await?; 149 | Ok(()) 150 | } 151 | 152 | pub(super) async fn finish(self, reader: &mut S) -> Result 153 | where 154 | S: AsyncRead + AsyncWrite + Unpin, 155 | { 156 | let result = reader.read_u32().await?; 157 | Ok(result.into()) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/client/connection.rs: -------------------------------------------------------------------------------- 1 | use futures::TryStreamExt; 2 | use tokio_stream::wrappers::ReceiverStream; 3 | 4 | use std::{future::Future, sync::Arc, vec}; 5 | use tokio::{ 6 | io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, 7 | sync::{ 8 | mpsc::{ 9 | channel, 10 | error::{TryRecvError, TrySendError}, 11 | Receiver, Sender, 12 | }, 13 | oneshot, Mutex, 14 | }, 15 | }; 16 | use tokio_util::compat::*; 17 | use tracing::*; 18 | 19 | use crate::{codec, PixelFormat, Rect, VncEncoding, VncError, VncEvent, X11Event}; 20 | const CHANNEL_SIZE: usize = 4096; 21 | 22 | #[cfg(not(target_arch = "wasm32"))] 23 | use tokio::spawn; 24 | #[cfg(target_arch = "wasm32")] 25 | use wasm_bindgen_futures::spawn_local as spawn; 26 | 27 | use super::messages::{ClientMsg, ServerMsg}; 28 | 29 | struct ImageRect { 30 | rect: Rect, 31 | encoding: VncEncoding, 32 | } 33 | 34 | impl From<[u8; 12]> for ImageRect { 35 | fn from(buf: [u8; 12]) -> Self { 36 | Self { 37 | rect: Rect { 38 | x: (buf[0] as u16) << 8 | buf[1] as u16, 39 | y: (buf[2] as u16) << 8 | buf[3] as u16, 40 | width: (buf[4] as u16) << 8 | buf[5] as u16, 41 | height: (buf[6] as u16) << 8 | buf[7] as u16, 42 | }, 43 | encoding: ((buf[8] as u32) << 24 44 | | (buf[9] as u32) << 16 45 | | (buf[10] as u32) << 8 46 | | (buf[11] as u32)) 47 | .into(), 48 | } 49 | } 50 | } 51 | 52 | impl ImageRect { 53 | async fn read(reader: &mut S) -> Result 54 | where 55 | S: AsyncRead + Unpin, 56 | { 57 | let mut rect_buf = [0_u8; 12]; 58 | reader.read_exact(&mut rect_buf).await?; 59 | Ok(rect_buf.into()) 60 | } 61 | } 62 | 63 | struct VncInner { 64 | name: String, 65 | screen: (u16, u16), 66 | input_ch: Sender, 67 | output_ch: Receiver, 68 | decoding_stop: Option>, 69 | net_conn_stop: Option>, 70 | closed: bool, 71 | } 72 | 73 | /// The instance of a connected vnc client 74 | 75 | impl VncInner { 76 | async fn new( 77 | mut stream: S, 78 | shared: bool, 79 | mut pixel_format: Option, 80 | encodings: Vec, 81 | ) -> Result 82 | where 83 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 84 | { 85 | let (conn_ch_tx, conn_ch_rx) = channel(CHANNEL_SIZE); 86 | let (input_ch_tx, input_ch_rx) = channel(CHANNEL_SIZE); 87 | let (output_ch_tx, output_ch_rx) = channel(CHANNEL_SIZE); 88 | let (decoding_stop_tx, decoding_stop_rx) = oneshot::channel(); 89 | let (net_conn_stop_tx, net_conn_stop_rx) = oneshot::channel(); 90 | 91 | trace!("client init msg"); 92 | send_client_init(&mut stream, shared).await?; 93 | 94 | trace!("server init msg"); 95 | let (name, (width, height)) = 96 | read_server_init(&mut stream, &mut pixel_format, &|e| async { 97 | output_ch_tx.send(e).await?; 98 | Ok(()) 99 | }) 100 | .await?; 101 | 102 | trace!("client encodings: {:?}", encodings); 103 | send_client_encoding(&mut stream, encodings).await?; 104 | 105 | trace!("Require the first frame"); 106 | input_ch_tx 107 | .send(ClientMsg::FramebufferUpdateRequest( 108 | Rect { 109 | x: 0, 110 | y: 0, 111 | width, 112 | height, 113 | }, 114 | 0, 115 | )) 116 | .await?; 117 | 118 | // start the decoding thread 119 | spawn(async move { 120 | trace!("Decoding thread starts"); 121 | let mut conn_ch_rx = { 122 | let conn_ch_rx = ReceiverStream::new(conn_ch_rx).into_async_read(); 123 | FuturesAsyncReadCompatExt::compat(conn_ch_rx) 124 | }; 125 | 126 | let output_func = |e| async { 127 | output_ch_tx.send(e).await?; 128 | Ok(()) 129 | }; 130 | 131 | let pf = pixel_format.as_ref().unwrap(); 132 | if let Err(e) = 133 | asycn_vnc_read_loop(&mut conn_ch_rx, pf, &output_func, decoding_stop_rx).await 134 | { 135 | if let VncError::IoError(e) = e { 136 | if let std::io::ErrorKind::UnexpectedEof = e.kind() { 137 | // this should be a normal case when the network connection disconnects 138 | // and we just send an EOF over the inner bridge between the process thread and the decode thread 139 | // do nothing here 140 | } else { 141 | error!("Error occurs during the decoding {:?}", e); 142 | let _ = output_func(VncEvent::Error(e.to_string())).await; 143 | } 144 | } else { 145 | error!("Error occurs during the decoding {:?}", e); 146 | let _ = output_func(VncEvent::Error(e.to_string())).await; 147 | } 148 | } 149 | trace!("Decoding thread stops"); 150 | }); 151 | 152 | // start the traffic process thread 153 | spawn(async move { 154 | trace!("Net Connection thread starts"); 155 | let _ = 156 | async_connection_process_loop(stream, input_ch_rx, conn_ch_tx, net_conn_stop_rx) 157 | .await; 158 | trace!("Net Connection thread stops"); 159 | }); 160 | 161 | info!("VNC Client {name} starts"); 162 | Ok(Self { 163 | name, 164 | screen: (width, height), 165 | input_ch: input_ch_tx, 166 | output_ch: output_ch_rx, 167 | decoding_stop: Some(decoding_stop_tx), 168 | net_conn_stop: Some(net_conn_stop_tx), 169 | closed: false, 170 | }) 171 | } 172 | 173 | async fn input(&mut self, event: X11Event) -> Result<(), VncError> { 174 | if self.closed { 175 | Err(VncError::ClientNotRunning) 176 | } else { 177 | let msg = match event { 178 | X11Event::Refresh => ClientMsg::FramebufferUpdateRequest( 179 | Rect { 180 | x: 0, 181 | y: 0, 182 | width: self.screen.0, 183 | height: self.screen.1, 184 | }, 185 | 1, 186 | ), 187 | X11Event::KeyEvent(key) => ClientMsg::KeyEvent(key.keycode, key.down), 188 | X11Event::PointerEvent(mouse) => { 189 | ClientMsg::PointerEvent(mouse.position_x, mouse.position_y, mouse.bottons) 190 | } 191 | X11Event::CopyText(text) => ClientMsg::ClientCutText(text), 192 | }; 193 | self.input_ch.send(msg).await?; 194 | Ok(()) 195 | } 196 | } 197 | 198 | async fn recv_event(&mut self) -> Result { 199 | if self.closed { 200 | Err(VncError::ClientNotRunning) 201 | } else { 202 | match self.output_ch.recv().await { 203 | Some(e) => Ok(e), 204 | None => { 205 | self.closed = true; 206 | Err(VncError::ClientNotRunning) 207 | } 208 | } 209 | } 210 | } 211 | 212 | async fn poll_event(&mut self) -> Result, VncError> { 213 | if self.closed { 214 | Err(VncError::ClientNotRunning) 215 | } else { 216 | match self.output_ch.try_recv() { 217 | Err(TryRecvError::Disconnected) => { 218 | self.closed = true; 219 | Err(VncError::ClientNotRunning) 220 | } 221 | Err(TryRecvError::Empty) => Ok(None), 222 | Ok(e) => Ok(Some(e)), 223 | } 224 | // Ok(self.output_ch.recv().await) 225 | } 226 | } 227 | 228 | /// Stop the VNC engine and release resources 229 | /// 230 | fn close(&mut self) -> Result<(), VncError> { 231 | if self.net_conn_stop.is_some() { 232 | let net_conn_stop: oneshot::Sender<()> = self.net_conn_stop.take().unwrap(); 233 | let _ = net_conn_stop.send(()); 234 | } 235 | if self.decoding_stop.is_some() { 236 | let decoding_stop = self.decoding_stop.take().unwrap(); 237 | let _ = decoding_stop.send(()); 238 | } 239 | self.closed = true; 240 | Ok(()) 241 | } 242 | } 243 | 244 | impl Drop for VncInner { 245 | fn drop(&mut self) { 246 | info!("VNC Client {} stops", self.name); 247 | let _ = self.close(); 248 | } 249 | } 250 | 251 | pub struct VncClient { 252 | inner: Arc>, 253 | } 254 | 255 | impl VncClient { 256 | pub(super) async fn new( 257 | stream: S, 258 | shared: bool, 259 | pixel_format: Option, 260 | encodings: Vec, 261 | ) -> Result 262 | where 263 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 264 | { 265 | Ok(Self { 266 | inner: Arc::new(Mutex::new( 267 | VncInner::new(stream, shared, pixel_format, encodings).await?, 268 | )), 269 | }) 270 | } 271 | 272 | /// Input a `X11Event` from the frontend 273 | /// 274 | pub async fn input(&self, event: X11Event) -> Result<(), VncError> { 275 | self.inner.lock().await.input(event).await 276 | } 277 | 278 | /// Receive a `VncEvent` from the engine 279 | /// This function will block until a `VncEvent` is received 280 | /// 281 | pub async fn recv_event(&self) -> Result { 282 | self.inner.lock().await.recv_event().await 283 | } 284 | 285 | /// polling `VncEvent` from the engine and give it to the client 286 | /// 287 | pub async fn poll_event(&self) -> Result, VncError> { 288 | self.inner.lock().await.poll_event().await 289 | } 290 | 291 | /// Stop the VNC engine and release resources 292 | /// 293 | pub async fn close(&self) -> Result<(), VncError> { 294 | self.inner.lock().await.close() 295 | } 296 | } 297 | 298 | impl Clone for VncClient { 299 | fn clone(&self) -> Self { 300 | Self { 301 | inner: self.inner.clone(), 302 | } 303 | } 304 | } 305 | 306 | async fn send_client_init(stream: &mut S, shared: bool) -> Result<(), VncError> 307 | where 308 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 309 | { 310 | trace!("Send shared flag: {}", shared); 311 | stream.write_u8(shared as u8).await?; 312 | Ok(()) 313 | } 314 | 315 | async fn read_server_init( 316 | stream: &mut S, 317 | pf: &mut Option, 318 | output_func: &F, 319 | ) -> Result<(String, (u16, u16)), VncError> 320 | where 321 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 322 | F: Fn(VncEvent) -> Fut, 323 | Fut: Future>, 324 | { 325 | // +--------------+--------------+------------------------------+ 326 | // | No. of bytes | Type [Value] | Description | 327 | // +--------------+--------------+------------------------------+ 328 | // | 2 | U16 | framebuffer-width in pixels | 329 | // | 2 | U16 | framebuffer-height in pixels | 330 | // | 16 | PIXEL_FORMAT | server-pixel-format | 331 | // | 4 | U32 | name-length | 332 | // | name-length | U8 array | name-string | 333 | // +--------------+--------------+------------------------------+ 334 | 335 | let screen_width = stream.read_u16().await?; 336 | let screen_height = stream.read_u16().await?; 337 | let mut send_our_pf = false; 338 | 339 | output_func(VncEvent::SetResolution( 340 | (screen_width, screen_height).into(), 341 | )) 342 | .await?; 343 | 344 | let pixel_format = PixelFormat::read(stream).await?; 345 | if pf.is_none() { 346 | output_func(VncEvent::SetPixelFormat(pixel_format)).await?; 347 | let _ = pf.insert(pixel_format); 348 | } else { 349 | send_our_pf = true; 350 | } 351 | 352 | let name_len = stream.read_u32().await?; 353 | let mut name_buf = vec![0_u8; name_len as usize]; 354 | stream.read_exact(&mut name_buf).await?; 355 | let name = String::from_utf8_lossy(&name_buf).into_owned(); 356 | 357 | if send_our_pf { 358 | trace!("Send customized pixel format {:#?}", pf); 359 | ClientMsg::SetPixelFormat(*pf.as_ref().unwrap()) 360 | .write(stream) 361 | .await?; 362 | } 363 | Ok((name, (screen_width, screen_height))) 364 | } 365 | 366 | async fn send_client_encoding( 367 | stream: &mut S, 368 | encodings: Vec, 369 | ) -> Result<(), VncError> 370 | where 371 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 372 | { 373 | ClientMsg::SetEncodings(encodings).write(stream).await?; 374 | Ok(()) 375 | } 376 | 377 | async fn asycn_vnc_read_loop( 378 | stream: &mut S, 379 | pf: &PixelFormat, 380 | output_func: &F, 381 | mut stop_ch: oneshot::Receiver<()>, 382 | ) -> Result<(), VncError> 383 | where 384 | S: AsyncRead + Unpin, 385 | F: Fn(VncEvent) -> Fut, 386 | Fut: Future>, 387 | { 388 | let mut raw_decoder = codec::RawDecoder::new(); 389 | let mut zrle_decoder = codec::ZrleDecoder::new(); 390 | let mut tight_decoder = codec::TightDecoder::new(); 391 | let mut trle_decoder = codec::TrleDecoder::new(); 392 | let mut cursor = codec::CursorDecoder::new(); 393 | 394 | // main decoding loop 395 | while let Err(oneshot::error::TryRecvError::Empty) = stop_ch.try_recv() { 396 | let server_msg = ServerMsg::read(stream).await?; 397 | trace!("Server message got: {:?}", server_msg); 398 | match server_msg { 399 | ServerMsg::FramebufferUpdate(rect_num) => { 400 | for _ in 0..rect_num { 401 | let rect = ImageRect::read(stream).await?; 402 | // trace!("Encoding: {:?}", rect.encoding); 403 | 404 | match rect.encoding { 405 | VncEncoding::Raw => { 406 | raw_decoder 407 | .decode(pf, &rect.rect, stream, output_func) 408 | .await?; 409 | } 410 | VncEncoding::CopyRect => { 411 | let source_x = stream.read_u16().await?; 412 | let source_y = stream.read_u16().await?; 413 | let mut src_rect = rect.rect; 414 | src_rect.x = source_x; 415 | src_rect.y = source_y; 416 | output_func(VncEvent::Copy(rect.rect, src_rect)).await?; 417 | } 418 | VncEncoding::Tight => { 419 | tight_decoder 420 | .decode(pf, &rect.rect, stream, output_func) 421 | .await?; 422 | } 423 | VncEncoding::Trle => { 424 | trle_decoder 425 | .decode(pf, &rect.rect, stream, output_func) 426 | .await?; 427 | } 428 | VncEncoding::Zrle => { 429 | zrle_decoder 430 | .decode(pf, &rect.rect, stream, output_func) 431 | .await?; 432 | } 433 | VncEncoding::CursorPseudo => { 434 | cursor.decode(pf, &rect.rect, stream, output_func).await?; 435 | } 436 | VncEncoding::DesktopSizePseudo => { 437 | output_func(VncEvent::SetResolution( 438 | (rect.rect.width, rect.rect.height).into(), 439 | )) 440 | .await?; 441 | } 442 | VncEncoding::LastRectPseudo => { 443 | break; 444 | } 445 | } 446 | } 447 | } 448 | // SetColorMapEntries, 449 | ServerMsg::Bell => { 450 | output_func(VncEvent::Bell).await?; 451 | } 452 | ServerMsg::ServerCutText(text) => { 453 | output_func(VncEvent::Text(text)).await?; 454 | } 455 | } 456 | } 457 | Ok(()) 458 | } 459 | 460 | async fn async_connection_process_loop( 461 | mut stream: S, 462 | mut input_ch: Receiver, 463 | conn_ch: Sender>>, 464 | mut stop_ch: oneshot::Receiver<()>, 465 | ) -> Result<(), VncError> 466 | where 467 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 468 | { 469 | let mut buffer = [0; 65535]; 470 | let mut pending = 0; 471 | 472 | // main traffic loop 473 | loop { 474 | if pending > 0 { 475 | match conn_ch.try_send(Ok(buffer[0..pending].to_owned())) { 476 | Err(TrySendError::Full(_message)) => (), 477 | Err(TrySendError::Closed(_message)) => break, 478 | Ok(()) => pending = 0, 479 | } 480 | } 481 | 482 | tokio::select! { 483 | _ = &mut stop_ch => break, 484 | result = stream.read(&mut buffer), if pending == 0 => { 485 | match result { 486 | Ok(nread) => { 487 | if nread > 0 { 488 | match conn_ch.try_send(Ok(buffer[0..nread].to_owned())) { 489 | Err(TrySendError::Full(_message)) => pending = nread, 490 | Err(TrySendError::Closed(_message)) => break, 491 | Ok(()) => () 492 | } 493 | } else { 494 | // According to the tokio's Doc 495 | // https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html 496 | // if nread == 0, then EOF is reached 497 | trace!("Net Connection EOF detected"); 498 | break; 499 | } 500 | } 501 | Err(e) => { 502 | error!("{}", e.to_string()); 503 | break; 504 | } 505 | } 506 | } 507 | Some(msg) = input_ch.recv() => { 508 | msg.write(&mut stream).await?; 509 | } 510 | } 511 | } 512 | 513 | // notify the decoding thread 514 | let _ = conn_ch 515 | .send(Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof))) 516 | .await; 517 | 518 | Ok(()) 519 | } 520 | -------------------------------------------------------------------------------- /src/client/connector.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | auth::{AuthHelper, AuthResult, SecurityType}, 3 | connection::VncClient, 4 | }; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite}; 8 | use tracing::{info, trace}; 9 | 10 | use crate::{PixelFormat, VncEncoding, VncError, VncVersion}; 11 | 12 | pub enum VncState 13 | where 14 | S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static, 15 | F: Future> + Send + Sync + 'static, 16 | { 17 | Handshake(VncConnector), 18 | Authenticate(VncConnector), 19 | Connected(VncClient), 20 | } 21 | 22 | impl VncState 23 | where 24 | S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static, 25 | F: Future> + Send + Sync + 'static, 26 | { 27 | pub fn try_start( 28 | self, 29 | ) -> Pin> + Send + Sync + 'static>> { 30 | Box::pin(async move { 31 | match self { 32 | VncState::Handshake(mut connector) => { 33 | // Read the rfbversion informed by the server 34 | let rfbversion = VncVersion::read(&mut connector.stream).await?; 35 | trace!( 36 | "Our version {:?}, server version {:?}", 37 | connector.rfb_version, 38 | rfbversion 39 | ); 40 | let rfbversion = if connector.rfb_version < rfbversion { 41 | connector.rfb_version 42 | } else { 43 | rfbversion 44 | }; 45 | 46 | // Record the negotiated rfbversion 47 | connector.rfb_version = rfbversion; 48 | trace!("Negotiated rfb version: {:?}", rfbversion); 49 | rfbversion.write(&mut connector.stream).await?; 50 | Ok(VncState::Authenticate(connector).try_start().await?) 51 | } 52 | VncState::Authenticate(mut connector) => { 53 | let security_types = 54 | SecurityType::read(&mut connector.stream, &connector.rfb_version).await?; 55 | 56 | assert!(!security_types.is_empty()); 57 | 58 | if security_types.contains(&SecurityType::None) { 59 | match connector.rfb_version { 60 | VncVersion::RFB33 => { 61 | // If the security-type is 1, for no authentication, the server does not 62 | // send the SecurityResult message but proceeds directly to the 63 | // initialization messages (Section 7.3). 64 | info!("No auth needed in vnc3.3"); 65 | } 66 | VncVersion::RFB37 => { 67 | // After the security handshake, if the security-type is 1, for no 68 | // authentication, the server does not send the SecurityResult message 69 | // but proceeds directly to the initialization messages (Section 7.3). 70 | info!("No auth needed in vnc3.7"); 71 | SecurityType::write(&SecurityType::None, &mut connector.stream) 72 | .await?; 73 | } 74 | VncVersion::RFB38 => { 75 | info!("No auth needed in vnc3.8"); 76 | SecurityType::write(&SecurityType::None, &mut connector.stream) 77 | .await?; 78 | let mut ok = [0; 4]; 79 | connector.stream.read_exact(&mut ok).await?; 80 | } 81 | } 82 | } else { 83 | // choose a auth method 84 | if security_types.contains(&SecurityType::VncAuth) { 85 | if connector.rfb_version != VncVersion::RFB33 { 86 | // In the security handshake (Section 7.1.2), rather than a two-way 87 | // negotiation, the server decides the security type and sends a single 88 | // word: 89 | 90 | // +--------------+--------------+---------------+ 91 | // | No. of bytes | Type [Value] | Description | 92 | // +--------------+--------------+---------------+ 93 | // | 4 | U32 | security-type | 94 | // +--------------+--------------+---------------+ 95 | 96 | // The security-type may only take the value 0, 1, or 2. A value of 0 97 | // means that the connection has failed and is followed by a string 98 | // giving the reason, as described in Section 7.1.2. 99 | SecurityType::write(&SecurityType::VncAuth, &mut connector.stream) 100 | .await?; 101 | } 102 | } else { 103 | let msg = "Security type apart from Vnc Auth has not been implemented"; 104 | return Err(VncError::General(msg.to_owned())); 105 | } 106 | 107 | // get password 108 | if connector.auth_methond.is_none() { 109 | return Err(VncError::NoPassword); 110 | } 111 | 112 | let credential = (connector.auth_methond.take().unwrap()).await?; 113 | 114 | // auth 115 | let auth = AuthHelper::read(&mut connector.stream, &credential).await?; 116 | auth.write(&mut connector.stream).await?; 117 | let result = auth.finish(&mut connector.stream).await?; 118 | if let AuthResult::Failed = result { 119 | if let VncVersion::RFB37 = connector.rfb_version { 120 | // In VNC Authentication (Section 7.2.2), if the authentication fails, 121 | // the server sends the SecurityResult message, but does not send an 122 | // error message before closing the connection. 123 | return Err(VncError::WrongPassword); 124 | } else { 125 | let _ = connector.stream.read_u32().await?; 126 | let mut err_msg = String::new(); 127 | connector.stream.read_to_string(&mut err_msg).await?; 128 | return Err(VncError::General(err_msg)); 129 | } 130 | } 131 | } 132 | info!("auth done, client connected"); 133 | 134 | Ok(VncState::Connected( 135 | VncClient::new( 136 | connector.stream, 137 | connector.allow_shared, 138 | connector.pixel_format, 139 | connector.encodings, 140 | ) 141 | .await?, 142 | )) 143 | } 144 | _ => unreachable!(), 145 | } 146 | }) 147 | } 148 | 149 | pub fn finish(self) -> Result { 150 | if let VncState::Connected(client) = self { 151 | Ok(client) 152 | } else { 153 | Err(VncError::ConnectError) 154 | } 155 | } 156 | } 157 | 158 | /// Connection Builder to setup a vnc client 159 | pub struct VncConnector 160 | where 161 | S: AsyncRead + AsyncWrite + Unpin + Send + 'static, 162 | F: Future> + Send + Sync + 'static, 163 | { 164 | stream: S, 165 | auth_methond: Option, 166 | rfb_version: VncVersion, 167 | allow_shared: bool, 168 | pixel_format: Option, 169 | encodings: Vec, 170 | } 171 | 172 | impl VncConnector 173 | where 174 | S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static, 175 | F: Future> + Send + Sync + 'static, 176 | { 177 | /// To new a vnc client configuration with stream `S` 178 | /// 179 | /// `S` should implement async I/O methods 180 | /// 181 | /// ```no_run 182 | /// use vnc::{PixelFormat, VncConnector, VncError}; 183 | /// use tokio::{self, net::TcpStream}; 184 | /// 185 | /// #[tokio::main] 186 | /// async fn main() -> Result<(), VncError> { 187 | /// let tcp = TcpStream::connect("127.0.0.1:5900").await?; 188 | /// let vnc = VncConnector::new(tcp) 189 | /// .set_auth_method(async move { Ok("password".to_string()) }) 190 | /// .add_encoding(vnc::VncEncoding::Tight) 191 | /// .add_encoding(vnc::VncEncoding::Zrle) 192 | /// .add_encoding(vnc::VncEncoding::CopyRect) 193 | /// .add_encoding(vnc::VncEncoding::Raw) 194 | /// .allow_shared(true) 195 | /// .set_pixel_format(PixelFormat::bgra()) 196 | /// .build()? 197 | /// .try_start() 198 | /// .await? 199 | /// .finish()?; 200 | /// Ok(()) 201 | /// } 202 | /// ``` 203 | /// 204 | pub fn new(stream: S) -> Self { 205 | Self { 206 | stream, 207 | auth_methond: None, 208 | allow_shared: true, 209 | rfb_version: VncVersion::RFB38, 210 | pixel_format: None, 211 | encodings: Vec::new(), 212 | } 213 | } 214 | 215 | /// An async callback which is used to query credentials if the vnc server has set 216 | /// 217 | /// ```no_compile 218 | /// connector = connector.set_auth_method(async move { Ok("password".to_string()) }) 219 | /// ``` 220 | /// 221 | /// if you're building a wasm app, 222 | /// the async callback also allows you to combine it to a promise 223 | /// 224 | /// ```no_compile 225 | /// #[wasm_bindgen] 226 | /// extern "C" { 227 | /// fn get_password() -> js_sys::Promise; 228 | /// } 229 | /// 230 | /// connector = connector 231 | /// .set_auth_method(async move { 232 | /// let auth = JsFuture::from(get_password()).await.unwrap(); 233 | /// Ok(auth.as_string().unwrap()) 234 | /// }); 235 | /// ``` 236 | /// 237 | /// While in the js code 238 | /// 239 | /// 240 | /// ```javascript 241 | /// var password = ''; 242 | /// function get_password() { 243 | /// return new Promise((reslove, reject) => { 244 | /// document.getElementById("submit_password").addEventListener("click", () => { 245 | /// password = window.document.getElementById("input_password").value 246 | /// reslove(password) 247 | /// }) 248 | /// }); 249 | /// } 250 | /// ``` 251 | /// 252 | /// The future won't be polled if the sever doesn't apply any password protections to the session 253 | /// 254 | pub fn set_auth_method(mut self, auth_callback: F) -> Self { 255 | self.auth_methond = Some(auth_callback); 256 | self 257 | } 258 | 259 | /// The max vnc version that we supported 260 | /// 261 | /// Version should be one of the [VncVersion] 262 | /// 263 | pub fn set_version(mut self, version: VncVersion) -> Self { 264 | self.rfb_version = version; 265 | self 266 | } 267 | 268 | /// Set the rgb order which you will use to resolve the image data 269 | /// 270 | /// In most of the case, use `PixelFormat::bgra()` on little endian PCs 271 | /// 272 | /// And use `PixelFormat::rgba()` on wasm apps (with canvas) 273 | /// 274 | /// Also, customized format is allowed 275 | /// 276 | /// Will use the default format informed by the vnc server if not set 277 | /// 278 | /// In this condition, the client will get a [crate::VncEvent::SetPixelFormat] event notified 279 | /// 280 | pub fn set_pixel_format(mut self, pf: PixelFormat) -> Self { 281 | self.pixel_format = Some(pf); 282 | self 283 | } 284 | 285 | /// Shared-flag is non-zero (true) if the server should try to share the 286 | /// 287 | /// desktop by leaving other clients connected, and zero (false) if it 288 | /// 289 | /// should give exclusive access to this client by disconnecting all 290 | /// 291 | /// other clients. 292 | /// 293 | pub fn allow_shared(mut self, allow_shared: bool) -> Self { 294 | self.allow_shared = allow_shared; 295 | self 296 | } 297 | 298 | /// Client encodings that we want to use 299 | /// 300 | /// One of [VncEncoding] 301 | /// 302 | /// [VncEncoding::Raw] must be sent as the RFC required 303 | /// 304 | /// The order to add encodings is the order to inform the server 305 | /// 306 | pub fn add_encoding(mut self, encoding: VncEncoding) -> Self { 307 | self.encodings.push(encoding); 308 | self 309 | } 310 | 311 | /// Complete the client configuration 312 | /// 313 | pub fn build(self) -> Result, VncError> { 314 | if self.encodings.is_empty() { 315 | return Err(VncError::NoEncoding); 316 | } 317 | Ok(VncState::Handshake(self)) 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/client/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncEncoding, VncError}; 2 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 3 | 4 | #[derive(Debug)] 5 | pub(super) enum ClientMsg { 6 | SetPixelFormat(PixelFormat), 7 | SetEncodings(Vec), 8 | FramebufferUpdateRequest(Rect, u8), 9 | KeyEvent(u32, bool), 10 | PointerEvent(u16, u16, u8), 11 | ClientCutText(String), 12 | } 13 | 14 | impl ClientMsg { 15 | pub(super) async fn write(self, writer: &mut S) -> Result<(), VncError> 16 | where 17 | S: AsyncWrite + Unpin, 18 | { 19 | match self { 20 | ClientMsg::SetPixelFormat(pf) => { 21 | // +--------------+--------------+--------------+ 22 | // | No. of bytes | Type [Value] | Description | 23 | // +--------------+--------------+--------------+ 24 | // | 1 | U8 [0] | message-type | 25 | // | 3 | | padding | 26 | // | 16 | PIXEL_FORMAT | pixel-format | 27 | // +--------------+--------------+--------------+ 28 | let mut payload = vec![0_u8, 0, 0, 0]; 29 | payload.extend(>>::into(pf)); 30 | writer.write_all(&payload).await?; 31 | Ok(()) 32 | } 33 | ClientMsg::SetEncodings(encodings) => { 34 | // +--------------+--------------+---------------------+ 35 | // | No. of bytes | Type [Value] | Description | 36 | // +--------------+--------------+---------------------+ 37 | // | 1 | U8 [2] | message-type | 38 | // | 1 | | padding | 39 | // | 2 | U16 | number-of-encodings | 40 | // +--------------+--------------+---------------------+ 41 | 42 | // This is followed by number-of-encodings repetitions of the following: 43 | // +--------------+--------------+---------------+ 44 | // | No. of bytes | Type [Value] | Description | 45 | // +--------------+--------------+---------------+ 46 | // | 4 | S32 | encoding-type | 47 | // +--------------+--------------+---------------+ 48 | let mut payload = vec![2, 0]; 49 | payload.extend_from_slice(&(encodings.len() as u16).to_be_bytes()); 50 | for e in encodings { 51 | payload.write_u32(e.into()).await?; 52 | } 53 | writer.write_all(&payload).await?; 54 | Ok(()) 55 | } 56 | ClientMsg::FramebufferUpdateRequest(rect, incremental) => { 57 | // +--------------+--------------+--------------+ 58 | // | No. of bytes | Type [Value] | Description | 59 | // +--------------+--------------+--------------+ 60 | // | 1 | U8 [3] | message-type | 61 | // | 1 | U8 | incremental | 62 | // | 2 | U16 | x-position | 63 | // | 2 | U16 | y-position | 64 | // | 2 | U16 | width | 65 | // | 2 | U16 | height | 66 | // +--------------+--------------+--------------+ 67 | let mut payload = vec![3, incremental]; 68 | payload.extend_from_slice(&rect.x.to_be_bytes()); 69 | payload.extend_from_slice(&rect.y.to_be_bytes()); 70 | payload.extend_from_slice(&rect.width.to_be_bytes()); 71 | payload.extend_from_slice(&rect.height.to_be_bytes()); 72 | writer.write_all(&payload).await?; 73 | Ok(()) 74 | } 75 | ClientMsg::KeyEvent(keycode, down) => { 76 | // +--------------+--------------+--------------+ 77 | // | No. of bytes | Type [Value] | Description | 78 | // +--------------+--------------+--------------+ 79 | // | 1 | U8 [4] | message-type | 80 | // | 1 | U8 | down-flag | 81 | // | 2 | | padding | 82 | // | 4 | U32 | key | 83 | // +--------------+--------------+--------------+ 84 | let mut payload = vec![4, down as u8, 0, 0]; 85 | payload.write_u32(keycode).await?; 86 | writer.write_all(&payload).await?; 87 | Ok(()) 88 | } 89 | ClientMsg::PointerEvent(x, y, mask) => { 90 | // +--------------+--------------+--------------+ 91 | // | No. of bytes | Type [Value] | Description | 92 | // +--------------+--------------+--------------+ 93 | // | 1 | U8 [5] | message-type | 94 | // | 1 | U8 | button-mask | 95 | // | 2 | U16 | x-position | 96 | // | 2 | U16 | y-position | 97 | // +--------------+--------------+--------------+ 98 | let mut payload = vec![5, mask]; 99 | payload.write_u16(x).await?; 100 | payload.write_u16(y).await?; 101 | writer.write_all(&payload).await?; 102 | Ok(()) 103 | } 104 | ClientMsg::ClientCutText(s) => { 105 | // +--------------+--------------+--------------+ 106 | // | No. of bytes | Type [Value] | Description | 107 | // +--------------+--------------+--------------+ 108 | // | 1 | U8 [6] | message-type | 109 | // | 3 | | padding | 110 | // | 4 | U32 | length | 111 | // | length | U8 array | text | 112 | // +--------------+--------------+--------------+ 113 | let mut payload = vec![6_u8, 0, 0, 0]; 114 | payload.write_u32(s.len() as u32).await?; 115 | payload.write_all(s.as_bytes()).await?; 116 | writer.write_all(&payload).await?; 117 | Ok(()) 118 | } 119 | } 120 | } 121 | } 122 | 123 | #[derive(Debug)] 124 | pub(super) enum ServerMsg { 125 | FramebufferUpdate(u16), 126 | // SetColorMapEntries, 127 | Bell, 128 | ServerCutText(String), 129 | } 130 | 131 | impl ServerMsg { 132 | pub(super) async fn read(reader: &mut S) -> Result 133 | where 134 | S: AsyncRead + Unpin, 135 | { 136 | let server_msg = reader.read_u8().await?; 137 | 138 | match server_msg { 139 | 0 => { 140 | // FramebufferUpdate 141 | // +--------------+--------------+----------------------+ 142 | // | No. of bytes | Type [Value] | Description | 143 | // +--------------+--------------+----------------------+ 144 | // | 1 | U8 [0] | message-type | 145 | // | 1 | | padding | 146 | // | 2 | U16 | number-of-rectangles | 147 | // +--------------+--------------+----------------------+ 148 | let _padding = reader.read_u8().await?; 149 | let rects = reader.read_u16().await?; 150 | Ok(ServerMsg::FramebufferUpdate(rects)) 151 | } 152 | 1 => { 153 | // SetColorMapEntries 154 | // +--------------+--------------+------------------+ 155 | // | No. of bytes | Type [Value] | Description | 156 | // +--------------+--------------+------------------+ 157 | // | 1 | U8 [1] | message-type | 158 | // | 1 | | padding | 159 | // | 2 | U16 | first-color | 160 | // | 2 | U16 | number-of-colors | 161 | // +--------------+--------------+------------------+ 162 | unimplemented!() 163 | } 164 | 2 => { 165 | // Bell 166 | // +--------------+--------------+--------------+ 167 | // | No. of bytes | Type [Value] | Description | 168 | // +--------------+--------------+--------------+ 169 | // | 1 | U8 [2] | message-type | 170 | // +--------------+--------------+--------------+ 171 | Ok(ServerMsg::Bell) 172 | } 173 | 3 => { 174 | // ServerCutText 175 | // +--------------+--------------+--------------+ 176 | // | No. of bytes | Type [Value] | Description | 177 | // +--------------+--------------+--------------+ 178 | // | 1 | U8 [3] | message-type | 179 | // | 3 | | padding | 180 | // | 4 | U32 | length | 181 | // | length | U8 array | text | 182 | // +--------------+--------------+--------------+ 183 | let mut padding = [0; 3]; 184 | reader.read_exact(&mut padding).await?; 185 | let len = reader.read_u32().await?; 186 | let mut buffer_str = vec![0; len as usize]; 187 | reader.read_exact(&mut buffer_str).await?; 188 | Ok(Self::ServerCutText( 189 | String::from_utf8_lossy(&buffer_str).to_string(), 190 | )) 191 | } 192 | _ => Err(VncError::WrongServerMessage), 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | mod auth; 2 | pub mod connection; 3 | pub mod connector; 4 | mod messages; 5 | mod security; 6 | 7 | pub use connection::VncClient; 8 | pub use connector::VncConnector; 9 | -------------------------------------------------------------------------------- /src/client/security/des.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Boucher, Antoni 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #![allow(dead_code)] 23 | 24 | pub type Key = [u8; 8]; 25 | 26 | const FIRST_BIT: u64 = 1 << 63; 27 | const HALF_KEY_SIZE: i64 = KEY_SIZE / 2; 28 | const KEY_SIZE: i64 = 56; 29 | 30 | /// Do a circular left shift on a width of HALF_KEY_SIZE. 31 | fn circular_left_shift(n1: u64, n2: u64, shift_count: i64) -> (u64, u64) { 32 | let mut new_value1 = n1; 33 | let mut new_value2 = n2; 34 | for _ in 0..shift_count { 35 | let first_bit = new_value1 & FIRST_BIT; 36 | new_value1 = (new_value1 << 1) | (first_bit >> (HALF_KEY_SIZE - 1)); 37 | let first_bit = new_value2 & FIRST_BIT; 38 | new_value2 = (new_value2 << 1) | (first_bit >> (HALF_KEY_SIZE - 1)); 39 | } 40 | (new_value1, new_value2) 41 | } 42 | 43 | /// Create the 16 subkeys. 44 | fn compute_subkeys(key: u64) -> Vec { 45 | let table = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]; 46 | let k0 = pc1(key); 47 | let mut subkeys = vec![k0]; 48 | 49 | for shift_count in &table { 50 | let last_key = subkeys.last().unwrap(); 51 | let last_ci = last_key & 0xFFFFFFF000000000; 52 | let last_di = last_key << HALF_KEY_SIZE; 53 | let (ci, di) = circular_left_shift(last_ci, last_di, *shift_count); 54 | let current_key = ci | (di >> HALF_KEY_SIZE); 55 | subkeys.push(current_key); 56 | } 57 | 58 | subkeys.remove(0); 59 | subkeys.iter().map(|&n| pc2(n)).collect() 60 | } 61 | 62 | /// Swap bits using the E table. 63 | fn e(block: u64) -> u64 { 64 | let table = [ 65 | 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 66 | 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1, 67 | ]; 68 | 69 | swap_bits(block, &table) 70 | } 71 | 72 | /// Decrypt `message` using the `key`. 73 | pub fn decrypt(cipher: &[u8], key: &Key) -> Vec { 74 | let key = key_to_u64(key); 75 | let mut subkeys = compute_subkeys(key); 76 | subkeys.reverse(); 77 | des(cipher, subkeys) 78 | } 79 | 80 | /// Encrypt `message` using `subkeys`. 81 | fn des(message: &[u8], subkeys: Vec) -> Vec { 82 | let blocks = message_to_u64s(message); 83 | 84 | let mut cipher = vec![]; 85 | 86 | for block in blocks { 87 | let permuted = ip(block); 88 | let mut li = permuted & 0xFFFFFFFF00000000; 89 | let mut ri = permuted << 32; 90 | 91 | for subkey in &subkeys { 92 | let last_li = li; 93 | li = ri; 94 | ri = last_li ^ feistel(ri, *subkey); 95 | } 96 | 97 | let r16l16 = ri | (li >> 32); 98 | cipher.append(&mut to_u8_vec(fp(r16l16))); 99 | } 100 | 101 | cipher 102 | } 103 | 104 | /// Encrypt `message` using the `key`. 105 | pub fn encrypt(message: &[u8], key: &Key) -> Vec { 106 | let key = key_to_u64(key); 107 | let subkeys = compute_subkeys(key); 108 | des(message, subkeys) 109 | } 110 | 111 | /// Feistel function. 112 | fn feistel(half_block: u64, subkey: u64) -> u64 { 113 | let expanded = e(half_block); 114 | let mut intermediate = expanded ^ subkey; 115 | let mut result = 0; 116 | 117 | for i in 0..8 { 118 | let block = (intermediate & 0xFC00000000000000) >> 58; 119 | intermediate <<= 6; 120 | result <<= 4; 121 | result |= s(i, block); 122 | } 123 | 124 | p(result << 32) 125 | } 126 | 127 | /// Swap bits using the IP table. 128 | fn ip(message: u64) -> u64 { 129 | let table = [ 130 | 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 131 | 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 132 | 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, 133 | ]; 134 | 135 | swap_bits(message, &table) 136 | } 137 | 138 | /// Convert a `Key` to a 64-bits integer. 139 | fn key_to_u64(key: &Key) -> u64 { 140 | let mut result = 0; 141 | for &part in key { 142 | result <<= 8; 143 | result += part as u64; 144 | } 145 | result 146 | } 147 | 148 | /// Convert a message to a vector of 64-bits integer. 149 | fn message_to_u64s(message: &[u8]) -> Vec { 150 | message.chunks(8).map(|m| key_to_u64(&to_key(m))).collect() 151 | } 152 | 153 | /// Swap bits using the P table. 154 | fn p(block: u64) -> u64 { 155 | let table = [ 156 | 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 157 | 19, 13, 30, 6, 22, 11, 4, 25, 158 | ]; 159 | 160 | swap_bits(block, &table) 161 | } 162 | 163 | /// Swap bits using the PC-1 table. 164 | fn pc1(key: u64) -> u64 { 165 | let table = [ 166 | 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 167 | 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 168 | 37, 29, 21, 13, 5, 28, 20, 12, 4, 169 | ]; 170 | 171 | swap_bits(key, &table) 172 | } 173 | 174 | /// Swap bits using the PC-2 table. 175 | fn pc2(key: u64) -> u64 { 176 | let table = [ 177 | 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 178 | 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32, 179 | ]; 180 | 181 | swap_bits(key, &table) 182 | } 183 | 184 | /// Swap bits using the reverse FP table. 185 | fn fp(message: u64) -> u64 { 186 | let table = [ 187 | 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 188 | 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 189 | 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 190 | ]; 191 | 192 | swap_bits(message, &table) 193 | } 194 | 195 | /// Produce 4-bits using an S box. 196 | fn s(box_id: usize, block: u64) -> u64 { 197 | let tables = [ 198 | [ 199 | [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], 200 | [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], 201 | [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], 202 | [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], 203 | ], 204 | [ 205 | [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10], 206 | [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5], 207 | [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15], 208 | [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], 209 | ], 210 | [ 211 | [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8], 212 | [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1], 213 | [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7], 214 | [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], 215 | ], 216 | [ 217 | [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15], 218 | [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9], 219 | [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4], 220 | [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], 221 | ], 222 | [ 223 | [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9], 224 | [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6], 225 | [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14], 226 | [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], 227 | ], 228 | [ 229 | [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11], 230 | [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8], 231 | [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6], 232 | [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], 233 | ], 234 | [ 235 | [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1], 236 | [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6], 237 | [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2], 238 | [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], 239 | ], 240 | [ 241 | [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7], 242 | [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2], 243 | [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8], 244 | [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11], 245 | ], 246 | ]; 247 | let i = ((block & 0x20) >> 4 | (block & 1)) as usize; 248 | let j = ((block & 0x1E) >> 1) as usize; 249 | tables[box_id][i][j] 250 | } 251 | 252 | /// Swap bits using a table. 253 | fn swap_bits(key: u64, table: &[u64]) -> u64 { 254 | let mut result = 0; 255 | 256 | for (pos, index) in table.iter().enumerate() { 257 | let bit = (key << (index - 1)) & FIRST_BIT; 258 | result |= bit >> pos; 259 | } 260 | 261 | result 262 | } 263 | 264 | /// Convert a slice to a `Key`. 265 | fn to_key(slice: &[u8]) -> Key { 266 | let mut vec: Vec = slice.to_vec(); 267 | let mut key = [0; 8]; 268 | let diff = key.len() - vec.len(); 269 | if diff > 0 { 270 | vec.append(&mut vec![0; diff]); 271 | } 272 | key.clone_from_slice(&vec); 273 | key 274 | } 275 | 276 | /// Convert a `u64` to a `Vec`. 277 | fn to_u8_vec(num: u64) -> Vec { 278 | vec![ 279 | ((num & 0xFF00000000000000) >> 56) as u8, 280 | ((num & 0x00FF000000000000) >> 48) as u8, 281 | ((num & 0x0000FF0000000000) >> 40) as u8, 282 | ((num & 0x000000FF00000000) >> 32) as u8, 283 | ((num & 0x00000000FF000000) >> 24) as u8, 284 | ((num & 0x0000000000FF0000) >> 16) as u8, 285 | ((num & 0x000000000000FF00) >> 8) as u8, 286 | (num & 0x00000000000000FF) as u8, 287 | ] 288 | } 289 | 290 | #[cfg(test)] 291 | mod tests { 292 | use super::{decrypt, encrypt}; 293 | 294 | #[test] 295 | fn test_encrypt_decrypt() { 296 | let key = [0x13, 0x34, 0x57, 0x79, 0x9B, 0xBC, 0xDF, 0xF1]; 297 | let message = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; 298 | let expected_cipher = vec![0x85, 0xE8, 0x13, 0x54, 0x0F, 0x0A, 0xB4, 0x05]; 299 | let cipher = encrypt(&message, &key); 300 | assert_eq!(cipher, expected_cipher); 301 | 302 | let cipher = expected_cipher; 303 | let expected_message = message; 304 | let message = decrypt(&cipher, &key); 305 | assert_eq!(message, expected_message); 306 | 307 | let message = [ 308 | 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 309 | 0xCD, 0xEF, 310 | ]; 311 | let expected_cipher = vec![ 312 | 0x85, 0xE8, 0x13, 0x54, 0x0F, 0x0A, 0xB4, 0x05, 0x85, 0xE8, 0x13, 0x54, 0x0F, 0x0A, 313 | 0xB4, 0x05, 314 | ]; 315 | let cipher = encrypt(&message, &key); 316 | assert_eq!(cipher, expected_cipher); 317 | 318 | let cipher = expected_cipher; 319 | let expected_message = message; 320 | let message = decrypt(&cipher, &key); 321 | assert_eq!(message, expected_message); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/client/security/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod des; 2 | -------------------------------------------------------------------------------- /src/codec/cursor.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncError, VncEvent}; 2 | use std::future::Future; 3 | use tokio::io::{AsyncRead, AsyncReadExt}; 4 | 5 | use super::uninit_vec; 6 | 7 | pub struct Decoder {} 8 | 9 | impl Decoder { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | 14 | pub async fn decode( 15 | &mut self, 16 | format: &PixelFormat, 17 | rect: &Rect, 18 | input: &mut S, 19 | output_func: &F, 20 | ) -> Result<(), VncError> 21 | where 22 | S: AsyncRead + Unpin, 23 | F: Fn(VncEvent) -> Fut, 24 | Fut: Future>, 25 | { 26 | let _hotx = rect.x; 27 | let _hoty = rect.y; 28 | let w = rect.width; 29 | let h = rect.height; 30 | 31 | let pixels_length = w as usize * h as usize * format.bits_per_pixel as usize / 8; 32 | let mask_length = (w as usize + 7) / 8 * h as usize; 33 | 34 | let _bytes = pixels_length + mask_length; 35 | 36 | let mut pixels = uninit_vec(pixels_length); 37 | input.read_exact(&mut pixels).await?; 38 | let mut mask = uninit_vec(mask_length); 39 | input.read_exact(&mut mask).await?; 40 | let mut image = uninit_vec(pixels_length); 41 | let mut pix_idx = 0; 42 | 43 | let pixel_mask = (format.red_max as u32) << format.red_shift 44 | | (format.green_max as u32) << format.green_shift 45 | | (format.blue_max as u32) << format.blue_shift; 46 | 47 | let mut alpha_idx = match pixel_mask { 48 | 0xff_ff_ff_00 => 3, 49 | 0xff_ff_00_ff => 2, 50 | 0xff_00_ff_ff => 1, 51 | 0x00_ff_ff_ff => 0, 52 | _ => unreachable!(), 53 | }; 54 | if format.big_endian_flag == 0 { 55 | alpha_idx = 3 - alpha_idx; 56 | } 57 | for y in 0..h as usize { 58 | for x in 0..w as usize { 59 | let mask_idx = y * ((w as usize + 7) / 8) + (x / 8); 60 | let alpha = if (mask[mask_idx] << (x % 8)) & 0x80 > 0 { 61 | 255 62 | } else { 63 | 0 64 | }; 65 | image[pix_idx] = pixels[pix_idx]; 66 | image[pix_idx + 1] = pixels[pix_idx + 1]; 67 | image[pix_idx + 2] = pixels[pix_idx + 2]; 68 | image[pix_idx + 3] = pixels[pix_idx + 3]; 69 | 70 | // use alpha from the bitmask to cover it. 71 | image[pix_idx + alpha_idx] = alpha; 72 | pix_idx += 4; 73 | } 74 | } 75 | 76 | output_func(VncEvent::SetCursor(*rect, image)).await?; 77 | 78 | Ok(()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | mod cursor; 2 | mod raw; 3 | mod tight; 4 | mod trle; 5 | mod zlib; 6 | mod zrle; 7 | pub(crate) use cursor::Decoder as CursorDecoder; 8 | pub(crate) use raw::Decoder as RawDecoder; 9 | pub(crate) use tight::Decoder as TightDecoder; 10 | pub(crate) use trle::Decoder as TrleDecoder; 11 | pub(crate) use zrle::Decoder as ZrleDecoder; 12 | 13 | fn uninit_vec(len: usize) -> Vec { 14 | let mut v = Vec::with_capacity(len); 15 | #[allow(clippy::uninit_vec)] 16 | unsafe { 17 | v.set_len(len) 18 | }; 19 | v 20 | } 21 | -------------------------------------------------------------------------------- /src/codec/raw.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncError, VncEvent}; 2 | use std::future::Future; 3 | use tokio::io::{AsyncRead, AsyncReadExt}; 4 | 5 | use super::uninit_vec; 6 | 7 | pub struct Decoder {} 8 | 9 | impl Decoder { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | 14 | pub async fn decode( 15 | &mut self, 16 | format: &PixelFormat, 17 | rect: &Rect, 18 | input: &mut S, 19 | output_func: &F, 20 | ) -> Result<(), VncError> 21 | where 22 | S: AsyncRead + Unpin, 23 | F: Fn(VncEvent) -> Fut, 24 | Fut: Future>, 25 | { 26 | // +----------------------------+--------------+-------------+ 27 | // | No. of bytes | Type [Value] | Description | 28 | // +----------------------------+--------------+-------------+ 29 | // | width*height*bytesPerPixel | PIXEL array | pixels | 30 | // +----------------------------+--------------+-------------+ 31 | let bpp = format.bits_per_pixel / 8; 32 | let buffer_size = bpp as usize * rect.height as usize * rect.width as usize; 33 | let mut pixels = uninit_vec(buffer_size); 34 | input.read_exact(&mut pixels).await?; 35 | output_func(VncEvent::RawImage(*rect, pixels)).await?; 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/codec/tight.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncError, VncEvent}; 2 | use std::future::Future; 3 | use std::io::Read; 4 | use tokio::io::{AsyncRead, AsyncReadExt}; 5 | use tracing::error; 6 | 7 | use super::{uninit_vec, zlib::ZlibReader}; 8 | 9 | const MAX_PALETTE: usize = 256; 10 | 11 | #[derive(Default)] 12 | pub struct Decoder { 13 | zlibs: [Option; 4], 14 | ctrl: u8, 15 | filter: u8, 16 | palette: Vec, 17 | alpha_shift: u32, 18 | } 19 | 20 | impl Decoder { 21 | pub fn new() -> Self { 22 | let mut new = Self { 23 | palette: Vec::with_capacity(MAX_PALETTE * 4), 24 | ..Default::default() 25 | }; 26 | for i in 0..4 { 27 | let decompressor = flate2::Decompress::new(true); 28 | new.zlibs[i] = Some(decompressor); 29 | } 30 | new 31 | } 32 | 33 | pub async fn decode( 34 | &mut self, 35 | format: &PixelFormat, 36 | rect: &Rect, 37 | input: &mut S, 38 | output_func: &F, 39 | ) -> Result<(), VncError> 40 | where 41 | S: AsyncRead + Unpin, 42 | F: Fn(VncEvent) -> Fut, 43 | Fut: Future>, 44 | { 45 | let pixel_mask = (format.red_max as u32) << format.red_shift 46 | | (format.green_max as u32) << format.green_shift 47 | | (format.blue_max as u32) << format.blue_shift; 48 | 49 | self.alpha_shift = match pixel_mask { 50 | 0xff_ff_ff_00 => 0, 51 | 0xff_ff_00_ff => 8, 52 | 0xff_00_ff_ff => 16, 53 | 0x00_ff_ff_ff => 24, 54 | _ => unreachable!(), 55 | }; 56 | 57 | let ctrl = input.read_u8().await?; 58 | for i in 0..4 { 59 | if (ctrl >> i) & 1 == 1 { 60 | self.zlibs[i].as_mut().unwrap().reset(true); 61 | } 62 | } 63 | 64 | // Figure out filter 65 | self.ctrl = ctrl >> 4; 66 | 67 | match self.ctrl { 68 | 8 => { 69 | // fill Rect 70 | self.fill_rect(format, rect, input, output_func).await 71 | } 72 | 9 => { 73 | // jpeg Rect 74 | self.jpeg_rect(format, rect, input, output_func).await 75 | } 76 | 10 => { 77 | // png Rect 78 | error!("PNG received in standard Tight rect"); 79 | Err(VncError::InvalidImageData) 80 | } 81 | x if x & 0x8 == 0 => { 82 | // basic Rect 83 | self.basic_rect(format, rect, input, output_func).await 84 | } 85 | _ => { 86 | error!("Illegal tight compression received ({})", self.ctrl); 87 | Err(VncError::InvalidImageData) 88 | } 89 | } 90 | } 91 | 92 | async fn read_data(&mut self, input: &mut S) -> Result, VncError> 93 | where 94 | S: AsyncRead + Unpin, 95 | { 96 | let len = { 97 | let mut len; 98 | let mut byte = input.read_u8().await? as usize; 99 | len = byte & 0x7f; 100 | if byte & 0x80 == 0x80 { 101 | byte = input.read_u8().await? as usize; 102 | len |= (byte & 0x7f) << 7; 103 | 104 | if byte & 0x80 == 0x80 { 105 | byte = input.read_u8().await? as usize; 106 | len |= byte << 14; 107 | } 108 | } 109 | len 110 | }; 111 | let mut data = uninit_vec(len); 112 | input.read_exact(&mut data).await?; 113 | Ok(data) 114 | } 115 | 116 | async fn fill_rect( 117 | &mut self, 118 | format: &PixelFormat, 119 | rect: &Rect, 120 | input: &mut S, 121 | output_func: &F, 122 | ) -> Result<(), VncError> 123 | where 124 | S: AsyncRead + Unpin, 125 | F: Fn(VncEvent) -> Fut, 126 | Fut: Future>, 127 | { 128 | let mut color = [0; 3]; 129 | input.read_exact(&mut color).await?; 130 | let bpp = format.bits_per_pixel as usize / 8; 131 | let mut image = Vec::with_capacity(rect.width as usize * rect.height as usize * bpp); 132 | 133 | let true_color = self.to_true_color(format, &color); 134 | 135 | for _ in 0..rect.width { 136 | for _ in 0..rect.height { 137 | image.extend_from_slice(&true_color); 138 | } 139 | } 140 | output_func(VncEvent::RawImage(*rect, image)).await?; 141 | Ok(()) 142 | } 143 | 144 | async fn jpeg_rect( 145 | &mut self, 146 | _format: &PixelFormat, 147 | rect: &Rect, 148 | input: &mut S, 149 | output_func: &F, 150 | ) -> Result<(), VncError> 151 | where 152 | S: AsyncRead + Unpin, 153 | F: Fn(VncEvent) -> Fut, 154 | Fut: Future>, 155 | { 156 | let data = self.read_data(input).await?; 157 | output_func(VncEvent::JpegImage(*rect, data)).await?; 158 | Ok(()) 159 | } 160 | 161 | async fn basic_rect( 162 | &mut self, 163 | format: &PixelFormat, 164 | rect: &Rect, 165 | input: &mut S, 166 | output_func: &F, 167 | ) -> Result<(), VncError> 168 | where 169 | S: AsyncRead + Unpin, 170 | F: Fn(VncEvent) -> Fut, 171 | Fut: Future>, 172 | { 173 | self.filter = { 174 | if self.ctrl & 0x4 == 4 { 175 | input.read_u8().await? 176 | } else { 177 | 0 178 | } 179 | }; 180 | 181 | let stream_id = self.ctrl & 0x3; 182 | match self.filter { 183 | 0 => { 184 | // copy filter 185 | self.copy_filter(stream_id, format, rect, input, output_func) 186 | .await 187 | } 188 | 1 => { 189 | // palette 190 | self.palette_filter(stream_id, format, rect, input, output_func) 191 | .await 192 | } 193 | 2 => { 194 | // gradient 195 | self.gradient_filter(stream_id, format, rect, input, output_func) 196 | .await 197 | } 198 | _ => { 199 | error!("Illegal tight filter received (filter: {})", self.filter); 200 | Err(VncError::InvalidImageData) 201 | } 202 | } 203 | } 204 | 205 | async fn copy_filter( 206 | &mut self, 207 | stream: u8, 208 | format: &PixelFormat, 209 | rect: &Rect, 210 | input: &mut S, 211 | output_func: &F, 212 | ) -> Result<(), VncError> 213 | where 214 | S: AsyncRead + Unpin, 215 | F: Fn(VncEvent) -> Fut, 216 | Fut: Future>, 217 | { 218 | let uncompressed_size = rect.width as usize * rect.height as usize * 3; 219 | if uncompressed_size == 0 { 220 | return Ok(()); 221 | }; 222 | 223 | let data = self 224 | .read_tight_data(stream, input, uncompressed_size) 225 | .await?; 226 | let mut image = Vec::with_capacity(uncompressed_size / 3 * 4); 227 | let mut j = 0; 228 | while j < uncompressed_size { 229 | image.extend_from_slice(&self.to_true_color(format, &data[j..j + 3])); 230 | j += 3; 231 | } 232 | 233 | output_func(VncEvent::RawImage(*rect, image)).await?; 234 | 235 | Ok(()) 236 | } 237 | 238 | async fn palette_filter( 239 | &mut self, 240 | stream: u8, 241 | format: &PixelFormat, 242 | rect: &Rect, 243 | input: &mut S, 244 | output_func: &F, 245 | ) -> Result<(), VncError> 246 | where 247 | S: AsyncRead + Unpin, 248 | F: Fn(VncEvent) -> Fut, 249 | Fut: Future>, 250 | { 251 | let num_colors = input.read_u8().await? as usize + 1; 252 | let palette_size = num_colors * 3; 253 | 254 | self.palette = uninit_vec(palette_size); 255 | input.read_exact(&mut self.palette).await?; 256 | 257 | let bpp = if num_colors <= 2 { 1 } else { 8 }; 258 | let row_size = (rect.width as usize * bpp + 7) / 8; 259 | let uncompressed_size = rect.height as usize * row_size; 260 | 261 | if uncompressed_size == 0 { 262 | return Ok(()); 263 | } 264 | 265 | let data = self 266 | .read_tight_data(stream, input, uncompressed_size) 267 | .await?; 268 | 269 | if num_colors == 2 { 270 | self.mono_rect(data, rect, format, output_func).await? 271 | } else { 272 | self.palette_rect(data, rect, format, output_func).await? 273 | } 274 | 275 | Ok(()) 276 | } 277 | 278 | async fn mono_rect( 279 | &mut self, 280 | data: Vec, 281 | rect: &Rect, 282 | format: &PixelFormat, 283 | output_func: &F, 284 | ) -> Result<(), VncError> 285 | where 286 | F: Fn(VncEvent) -> Fut, 287 | Fut: Future>, 288 | { 289 | // Convert indexed (palette based) image data to RGB 290 | let total = rect.width as usize * rect.height as usize; 291 | let mut image = uninit_vec(total * 4); 292 | let mut offset = 8_usize; 293 | let mut index = -1_isize; 294 | let mut dp = 0; 295 | for i in 0..total { 296 | if offset == 0 || i % rect.width as usize == 0 { 297 | offset = 8; 298 | index += 1; 299 | } 300 | offset -= 1; 301 | let sp = ((data[index as usize] >> offset) & 0x01) as usize * 3; 302 | let true_color = self.to_true_color(format, &self.palette[sp..sp + 3]); 303 | unsafe { 304 | std::ptr::copy_nonoverlapping(true_color.as_ptr(), image.as_mut_ptr().add(dp), 4) 305 | } 306 | dp += 4; 307 | } 308 | output_func(VncEvent::RawImage(*rect, image)).await?; 309 | Ok(()) 310 | } 311 | 312 | async fn palette_rect( 313 | &mut self, 314 | data: Vec, 315 | rect: &Rect, 316 | format: &PixelFormat, 317 | output_func: &F, 318 | ) -> Result<(), VncError> 319 | where 320 | F: Fn(VncEvent) -> Fut, 321 | Fut: Future>, 322 | { 323 | // Convert indexed (palette based) image data to RGB 324 | let total = rect.width as usize * rect.height as usize; 325 | let mut image = uninit_vec(total * 4); 326 | let mut i = 0; 327 | let mut dp = 0; 328 | while i < total { 329 | let sp = data[i] as usize * 3; 330 | let true_color = self.to_true_color(format, &self.palette[sp..sp + 3]); 331 | unsafe { 332 | std::ptr::copy_nonoverlapping(true_color.as_ptr(), image.as_mut_ptr().add(dp), 4) 333 | } 334 | dp += 4; 335 | i += 1; 336 | } 337 | output_func(VncEvent::RawImage(*rect, image)).await?; 338 | Ok(()) 339 | } 340 | 341 | async fn gradient_filter( 342 | &mut self, 343 | stream: u8, 344 | format: &PixelFormat, 345 | rect: &Rect, 346 | input: &mut S, 347 | output_func: &F, 348 | ) -> Result<(), VncError> 349 | where 350 | S: AsyncRead + Unpin, 351 | F: Fn(VncEvent) -> Fut, 352 | Fut: Future>, 353 | { 354 | let uncompressed_size = rect.width as usize * rect.height as usize * 3; 355 | if uncompressed_size == 0 { 356 | return Ok(()); 357 | }; 358 | let data = self 359 | .read_tight_data(stream, input, uncompressed_size) 360 | .await?; 361 | let mut image = uninit_vec(rect.width as usize * rect.height as usize * 4); 362 | 363 | let row_len = rect.width as usize * 3 + 3; 364 | let mut row_0 = vec![0_u16; row_len]; 365 | let mut row_1 = vec![0_u16; row_len]; 366 | let max = [format.red_max, format.green_max, format.blue_max]; 367 | let shift = [format.red_shift, format.green_shift, format.blue_shift]; 368 | let mut sp = 0; 369 | let mut dp = 0; 370 | 371 | for y in 0..rect.height as usize { 372 | let (this_row, prev_row) = match y & 1 { 373 | 0 => (&mut row_0, &mut row_1), 374 | 1 => (&mut row_1, &mut row_0), 375 | _ => unreachable!(), 376 | }; 377 | let mut x = 3; 378 | while x < row_len { 379 | let rgb = &data[sp..sp + 3]; 380 | let mut color = 0; 381 | for index in 0..3 { 382 | let d = prev_row[index + x] as i32 + this_row[index + x - 3] as i32 383 | - prev_row[index + x - 3] as i32; 384 | let converted = if d < 0 { 385 | 0 386 | } else if d > max[index] as i32 { 387 | max[index] 388 | } else { 389 | d as u16 390 | }; 391 | this_row[index + x] = (converted + rgb[index] as u16) & max[index]; 392 | color |= (this_row[x + index] as u32 & max[index] as u32) << shift[index]; 393 | } 394 | unsafe { 395 | std::ptr::copy_nonoverlapping( 396 | color.to_le_bytes().as_ptr(), 397 | image.as_mut_ptr().add(dp), 398 | 4, 399 | ) 400 | } 401 | dp += 4; 402 | sp += 3; 403 | x += 3; 404 | } 405 | } 406 | 407 | output_func(VncEvent::RawImage(*rect, image)).await?; 408 | Ok(()) 409 | } 410 | 411 | async fn read_tight_data( 412 | &mut self, 413 | stream: u8, 414 | input: &mut S, 415 | uncompressed_size: usize, 416 | ) -> Result, VncError> 417 | where 418 | S: AsyncRead + Unpin, 419 | { 420 | let mut data; 421 | if uncompressed_size < 12 { 422 | data = uninit_vec(uncompressed_size); 423 | input.read_exact(&mut data).await?; 424 | } else { 425 | let d = self.read_data(input).await?; 426 | let mut reader = ZlibReader::new(self.zlibs[stream as usize].take().unwrap(), &d); 427 | data = uninit_vec(uncompressed_size); 428 | reader.read_exact(&mut data)?; 429 | self.zlibs[stream as usize] = Some(reader.into_inner()?); 430 | }; 431 | Ok(data) 432 | } 433 | 434 | fn to_true_color(&self, format: &PixelFormat, color: &[u8]) -> [u8; 4] { 435 | let alpha = 255; 436 | // always rgb 437 | (((color[0] as u32 & format.red_max as u32) << format.red_shift) 438 | | ((color[1] as u32 & format.green_max as u32) << format.green_shift) 439 | | ((color[2] as u32 & format.blue_max as u32) << format.blue_shift) 440 | | ((alpha as u32) << self.alpha_shift)) 441 | .to_le_bytes() 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/codec/trle.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncError, VncEvent}; 2 | use std::future::Future; 3 | use tokio::io::{AsyncRead, AsyncReadExt}; 4 | use tracing::error; 5 | 6 | use super::uninit_vec; 7 | 8 | async fn read_run_length(reader: &mut S) -> Result 9 | where 10 | S: AsyncRead + Unpin, 11 | { 12 | let mut run_length_part; 13 | let mut run_length = 1; 14 | loop { 15 | run_length_part = reader.read_u8().await?; 16 | run_length += run_length_part as usize; 17 | if 255 != run_length_part { 18 | break; 19 | } 20 | } 21 | Ok(run_length) 22 | } 23 | 24 | async fn copy_true_color( 25 | reader: &mut S, 26 | pixels: &mut Vec, 27 | pad: bool, 28 | compressed_bpp: usize, 29 | bpp: usize, 30 | ) -> Result<(), VncError> 31 | where 32 | S: AsyncRead + Unpin, 33 | { 34 | let mut buf = [255; 4]; 35 | reader 36 | .read_exact(&mut buf[pad as usize..pad as usize + compressed_bpp]) 37 | .await?; 38 | pixels.extend_from_slice(&buf[..bpp]); 39 | Ok(()) 40 | } 41 | 42 | fn copy_indexed(palette: &[u8], pixels: &mut Vec, bpp: usize, index: u8) { 43 | let start = index as usize * bpp; 44 | pixels.extend_from_slice(&palette[start..start + bpp]) 45 | } 46 | 47 | pub struct Decoder {} 48 | 49 | impl Decoder { 50 | pub fn new() -> Self { 51 | Self {} 52 | } 53 | 54 | pub async fn decode( 55 | &mut self, 56 | format: &PixelFormat, 57 | rect: &Rect, 58 | input: &mut S, 59 | output_func: &F, 60 | ) -> Result<(), VncError> 61 | where 62 | S: AsyncRead + Unpin, 63 | F: Fn(VncEvent) -> Fut, 64 | Fut: Future>, 65 | { 66 | let data_len = input.read_u32().await? as usize; 67 | let mut zlib_data = uninit_vec(data_len); 68 | input.read_exact(&mut zlib_data).await?; 69 | 70 | let bpp = format.bits_per_pixel as usize / 8; 71 | let pixel_mask = (format.red_max as u32) << format.red_shift 72 | | (format.green_max as u32) << format.green_shift 73 | | (format.blue_max as u32) << format.blue_shift; 74 | 75 | let (compressed_bpp, alpha_at_first) = 76 | if format.bits_per_pixel == 32 && format.true_color_flag > 0 && format.depth <= 24 { 77 | if pixel_mask & 0x000000ff == 0 { 78 | // rgb at the most significant bits 79 | // if format.big_endian_flag is set 80 | // then decompressed data is excepted to be [rgb.0, rgb.1, rgb.2, alpha] 81 | // otherwise the decompressed data should be [alpha, rgb.0, rgb.1, rgb.2] 82 | (3, format.big_endian_flag == 0) 83 | } else if pixel_mask & 0xff000000 == 0 { 84 | // rgb at the least significant bits 85 | // if format.big_endian_flag is set 86 | // then decompressed data should be [alpha, rgb.0, rgb.1, rgb.2] 87 | // otherwise the decompressed data should be [rgb.0, rgb.1, rgb.2, alpha] 88 | (3, format.big_endian_flag > 0) 89 | } else { 90 | (4, false) 91 | } 92 | } else { 93 | (bpp, false) 94 | }; 95 | let mut palette = Vec::with_capacity(128 * bpp); 96 | 97 | let mut y = 0; 98 | while y < rect.height { 99 | let height = if y + 64 > rect.height { 100 | rect.height - y 101 | } else { 102 | 64 103 | }; 104 | let mut x = 0; 105 | while x < rect.width { 106 | let width = if x + 64 > rect.width { 107 | rect.width - x 108 | } else { 109 | 64 110 | }; 111 | let pixel_count = height as usize * width as usize; 112 | 113 | let control = input.read_u8().await?; 114 | let is_rle = control & 0x80 > 0; 115 | let palette_size = control & 0x7f; 116 | palette.truncate(0); 117 | 118 | for _ in 0..palette_size { 119 | copy_true_color(input, &mut palette, alpha_at_first, compressed_bpp, bpp) 120 | .await? 121 | } 122 | 123 | let mut pixels = Vec::with_capacity(pixel_count * bpp); 124 | match (is_rle, palette_size) { 125 | (false, 0) => { 126 | // True Color pixels 127 | for _ in 0..pixel_count { 128 | copy_true_color(input, &mut pixels, alpha_at_first, compressed_bpp, bpp) 129 | .await? 130 | } 131 | } 132 | (false, 1) => { 133 | // Color fill 134 | for _ in 0..pixel_count { 135 | copy_indexed(&palette, &mut pixels, bpp, 0) 136 | } 137 | } 138 | (false, 2..=16) => { 139 | // Indexed pixels 140 | let bits_per_index = match palette_size { 141 | 2 => 1, 142 | 3..=4 => 2, 143 | 5..=16 => 4, 144 | _ => unreachable!(), 145 | }; 146 | let mut encoded = input.read_u8().await?; 147 | let mask = (1 << bits_per_index) - 1; 148 | 149 | for y in 0..height { 150 | let mut shift = 8 - bits_per_index; 151 | for _ in 0..width { 152 | if shift < 0 { 153 | shift = 8 - bits_per_index; 154 | encoded = input.read_u8().await?; 155 | } 156 | let idx = (encoded >> shift) & mask; 157 | 158 | copy_indexed(&palette, &mut pixels, bpp, idx); 159 | shift -= bits_per_index; 160 | } 161 | if shift < 8 - bits_per_index && y < height - 1 { 162 | encoded = input.read_u8().await?; 163 | } 164 | } 165 | } 166 | (true, 0) => { 167 | // True Color RLE 168 | let mut count = 0; 169 | let mut pixel = Vec::new(); 170 | while count < pixel_count { 171 | pixel.truncate(0); 172 | copy_true_color(input, &mut pixel, alpha_at_first, compressed_bpp, bpp) 173 | .await?; 174 | let run_length = read_run_length(input).await?; 175 | for _ in 0..run_length { 176 | pixels.extend(&pixel) 177 | } 178 | count += run_length; 179 | } 180 | } 181 | (true, 2..=127) => { 182 | // Indexed RLE 183 | let mut count = 0; 184 | while count < pixel_count { 185 | let control = input.read_u8().await?; 186 | let longer_than_one = control & 0x80 > 0; 187 | let index = control & 0x7f; 188 | let run_length = if longer_than_one { 189 | read_run_length(input).await? 190 | } else { 191 | 1 192 | }; 193 | for _ in 0..run_length { 194 | copy_indexed(&palette, &mut pixels, bpp, index); 195 | } 196 | count += run_length; 197 | } 198 | } 199 | (x, y) => { 200 | error!("TLRE subencoding error {:?}", (x, y)); 201 | return Err(VncError::InvalidImageData); 202 | } 203 | } 204 | output_func(VncEvent::RawImage( 205 | Rect { 206 | x: rect.x + x, 207 | y: rect.y + y, 208 | width, 209 | height, 210 | }, 211 | pixels, 212 | )) 213 | .await?; 214 | x += width; 215 | } 216 | y += height; 217 | } 218 | 219 | Ok(()) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/codec/zlib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 whitequark 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | The above copyright notice and this permission notice 13 | shall be included in all copies or substantial portions 14 | of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | use std::io::{Read, Result}; 27 | 28 | pub struct ZlibReader<'a> { 29 | decompressor: flate2::Decompress, 30 | input: &'a [u8], 31 | } 32 | 33 | impl<'a> ZlibReader<'a> { 34 | pub fn new(decompressor: flate2::Decompress, input: &'a [u8]) -> ZlibReader<'a> { 35 | ZlibReader { 36 | decompressor, 37 | input, 38 | } 39 | } 40 | 41 | pub fn into_inner(self) -> Result { 42 | if self.input.is_empty() { 43 | Ok(self.decompressor) 44 | } else { 45 | Err(std::io::Error::new( 46 | std::io::ErrorKind::InvalidData, 47 | "leftover zlib byte data", 48 | )) 49 | } 50 | } 51 | 52 | pub fn read_u8(&mut self) -> std::io::Result { 53 | let mut buf = [0; 1]; 54 | self.read_exact(&mut buf)?; 55 | Ok(buf[0]) 56 | } 57 | } 58 | 59 | impl<'a> Read for ZlibReader<'a> { 60 | fn read(&mut self, output: &mut [u8]) -> std::io::Result { 61 | let in_before = self.decompressor.total_in(); 62 | let out_before = self.decompressor.total_out(); 63 | let result = 64 | self.decompressor 65 | .decompress(self.input, output, flate2::FlushDecompress::None); 66 | let consumed = (self.decompressor.total_in() - in_before) as usize; 67 | let produced = (self.decompressor.total_out() - out_before) as usize; 68 | 69 | self.input = &self.input[consumed..]; 70 | match result { 71 | Ok(flate2::Status::Ok) => Ok(produced), 72 | Ok(flate2::Status::BufError) => Ok(0), 73 | Err(error) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error)), 74 | Ok(flate2::Status::StreamEnd) => Err(std::io::Error::new( 75 | std::io::ErrorKind::InvalidData, 76 | "zlib stream end", 77 | )), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/codec/zrle.rs: -------------------------------------------------------------------------------- 1 | use crate::{PixelFormat, Rect, VncError, VncEvent}; 2 | use std::future::Future; 3 | use tokio::io::{AsyncRead, AsyncReadExt}; 4 | use tracing::error; 5 | 6 | use super::{uninit_vec, zlib::ZlibReader}; 7 | 8 | fn read_run_length(reader: &mut ZlibReader) -> Result { 9 | let mut run_length_part; 10 | let mut run_length = 1; 11 | loop { 12 | run_length_part = reader.read_u8()?; 13 | run_length += run_length_part as usize; 14 | if 255 != run_length_part { 15 | break; 16 | } 17 | } 18 | Ok(run_length) 19 | } 20 | 21 | fn copy_true_color( 22 | reader: &mut ZlibReader, 23 | pixels: &mut Vec, 24 | pad: bool, 25 | compressed_bpp: usize, 26 | bpp: usize, 27 | ) -> Result<(), VncError> { 28 | let mut buf = [255; 4]; 29 | std::io::Read::read_exact( 30 | reader, 31 | &mut buf[pad as usize..pad as usize + compressed_bpp], 32 | )?; 33 | pixels.extend_from_slice(&buf[..bpp]); 34 | Ok(()) 35 | } 36 | 37 | fn copy_indexed(palette: &[u8], pixels: &mut Vec, bpp: usize, index: u8) { 38 | let start = index as usize * bpp; 39 | pixels.extend_from_slice(&palette[start..start + bpp]) 40 | } 41 | 42 | pub struct Decoder { 43 | decompressor: Option, 44 | } 45 | 46 | impl Decoder { 47 | pub fn new() -> Self { 48 | Self { 49 | decompressor: Some(flate2::Decompress::new(true)), 50 | } 51 | } 52 | 53 | pub async fn decode( 54 | &mut self, 55 | format: &PixelFormat, 56 | rect: &Rect, 57 | input: &mut S, 58 | output_func: &F, 59 | ) -> Result<(), VncError> 60 | where 61 | S: AsyncRead + Unpin, 62 | F: Fn(VncEvent) -> Fut, 63 | Fut: Future>, 64 | { 65 | let data_len = input.read_u32().await? as usize; 66 | let mut zlib_data = uninit_vec(data_len); 67 | input.read_exact(&mut zlib_data).await?; 68 | let decompressor = self.decompressor.take().unwrap(); 69 | let mut reader = ZlibReader::new(decompressor, &zlib_data); 70 | 71 | let bpp = format.bits_per_pixel as usize / 8; 72 | let pixel_mask = (format.red_max as u32) << format.red_shift 73 | | (format.green_max as u32) << format.green_shift 74 | | (format.blue_max as u32) << format.blue_shift; 75 | 76 | let (compressed_bpp, alpha_at_first) = 77 | if format.bits_per_pixel == 32 && format.true_color_flag > 0 && format.depth <= 24 { 78 | if pixel_mask & 0x000000ff == 0 { 79 | // rgb at the most significant bits 80 | // if format.big_endian_flag is set 81 | // then decompressed data is excepted to be [rgb.0, rgb.1, rgb.2, alpha] 82 | // otherwise the decompressed data should be [alpha, rgb.0, rgb.1, rgb.2] 83 | (3, format.big_endian_flag == 0) 84 | } else if pixel_mask & 0xff000000 == 0 { 85 | // rgb at the least significant bits 86 | // if format.big_endian_flag is set 87 | // then decompressed data should be [alpha, rgb.0, rgb.1, rgb.2] 88 | // otherwise the decompressed data should be [rgb.0, rgb.1, rgb.2, alpha] 89 | (3, format.big_endian_flag > 0) 90 | } else { 91 | (4, false) 92 | } 93 | } else { 94 | (bpp, false) 95 | }; 96 | let mut palette = Vec::with_capacity(128 * bpp); 97 | 98 | let mut y = 0; 99 | while y < rect.height { 100 | let height = if y + 64 > rect.height { 101 | rect.height - y 102 | } else { 103 | 64 104 | }; 105 | let mut x = 0; 106 | while x < rect.width { 107 | let width = if x + 64 > rect.width { 108 | rect.width - x 109 | } else { 110 | 64 111 | }; 112 | let pixel_count = height as usize * width as usize; 113 | 114 | let control = reader.read_u8()?; 115 | let is_rle = control & 0x80 > 0; 116 | let palette_size = control & 0x7f; 117 | palette.truncate(0); 118 | 119 | for _ in 0..palette_size { 120 | copy_true_color( 121 | &mut reader, 122 | &mut palette, 123 | alpha_at_first, 124 | compressed_bpp, 125 | bpp, 126 | )? 127 | } 128 | 129 | let mut pixels = Vec::with_capacity(pixel_count * bpp); 130 | match (is_rle, palette_size) { 131 | (false, 0) => { 132 | // True Color pixels 133 | for _ in 0..pixel_count { 134 | copy_true_color( 135 | &mut reader, 136 | &mut pixels, 137 | alpha_at_first, 138 | compressed_bpp, 139 | bpp, 140 | )? 141 | } 142 | } 143 | (false, 1) => { 144 | // Color fill 145 | for _ in 0..pixel_count { 146 | copy_indexed(&palette, &mut pixels, bpp, 0) 147 | } 148 | } 149 | (false, 2..=16) => { 150 | // Indexed pixels 151 | let bits_per_index = match palette_size { 152 | 2 => 1, 153 | 3..=4 => 2, 154 | 5..=16 => 4, 155 | _ => unreachable!(), 156 | }; 157 | let mut encoded = reader.read_u8()?; 158 | let mask = (1 << bits_per_index) - 1; 159 | 160 | for y in 0..height { 161 | let mut shift = 8 - bits_per_index; 162 | for _ in 0..width { 163 | if shift < 0 { 164 | shift = 8 - bits_per_index; 165 | encoded = reader.read_u8()?; 166 | } 167 | let idx = (encoded >> shift) & mask; 168 | 169 | copy_indexed(&palette, &mut pixels, bpp, idx); 170 | shift -= bits_per_index; 171 | } 172 | if shift < 8 - bits_per_index && y < height - 1 { 173 | encoded = reader.read_u8()?; 174 | } 175 | } 176 | } 177 | (true, 0) => { 178 | // True Color RLE 179 | let mut count = 0; 180 | let mut pixel = Vec::new(); 181 | while count < pixel_count { 182 | pixel.truncate(0); 183 | copy_true_color( 184 | &mut reader, 185 | &mut pixel, 186 | alpha_at_first, 187 | compressed_bpp, 188 | bpp, 189 | )?; 190 | let run_length = read_run_length(&mut reader)?; 191 | for _ in 0..run_length { 192 | pixels.extend(&pixel) 193 | } 194 | count += run_length; 195 | } 196 | } 197 | (true, 2..=127) => { 198 | // Indexed RLE 199 | let mut count = 0; 200 | while count < pixel_count { 201 | let control = reader.read_u8()?; 202 | let longer_than_one = control & 0x80 > 0; 203 | let index = control & 0x7f; 204 | let run_length = if longer_than_one { 205 | read_run_length(&mut reader)? 206 | } else { 207 | 1 208 | }; 209 | for _ in 0..run_length { 210 | copy_indexed(&palette, &mut pixels, bpp, index); 211 | } 212 | count += run_length; 213 | } 214 | } 215 | (x, y) => { 216 | error!("ZRLE subencoding error {:?}", (x, y)); 217 | return Err(VncError::InvalidImageData); 218 | } 219 | } 220 | output_func(VncEvent::RawImage( 221 | Rect { 222 | x: rect.x + x, 223 | y: rect.y + y, 224 | width, 225 | height, 226 | }, 227 | pixels, 228 | )) 229 | .await?; 230 | x += width; 231 | } 232 | y += height; 233 | } 234 | 235 | self.decompressor = Some(reader.into_inner()?); 236 | 237 | Ok(()) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::VncError; 2 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 3 | 4 | /// All supported vnc encodings 5 | #[allow(dead_code)] 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | #[repr(i32)] 8 | pub enum VncEncoding { 9 | Raw = 0, 10 | CopyRect = 1, 11 | // Rre = 2, 12 | // Hextile = 5, 13 | Tight = 7, 14 | Trle = 15, 15 | Zrle = 16, 16 | CursorPseudo = -239, 17 | DesktopSizePseudo = -223, 18 | LastRectPseudo = -224, 19 | } 20 | 21 | impl From for VncEncoding { 22 | fn from(num: u32) -> Self { 23 | unsafe { std::mem::transmute(num) } 24 | } 25 | } 26 | 27 | impl From for u32 { 28 | fn from(e: VncEncoding) -> Self { 29 | e as u32 30 | } 31 | } 32 | 33 | /// All supported vnc versions 34 | #[allow(dead_code)] 35 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq)] 36 | #[repr(u8)] 37 | pub enum VncVersion { 38 | RFB33, 39 | RFB37, 40 | RFB38, 41 | } 42 | 43 | impl From<[u8; 12]> for VncVersion { 44 | fn from(version: [u8; 12]) -> Self { 45 | match &version { 46 | b"RFB 003.003\n" => VncVersion::RFB33, 47 | b"RFB 003.007\n" => VncVersion::RFB37, 48 | b"RFB 003.008\n" => VncVersion::RFB38, 49 | // https://www.rfc-editor.org/rfc/rfc6143#section-7.1.1 50 | // Other version numbers are reported by some servers and clients, 51 | // but should be interpreted as 3.3 since they do not implement the 52 | // different handshake in 3.7 or 3.8. 53 | _ => VncVersion::RFB33, 54 | } 55 | } 56 | } 57 | 58 | impl From for &[u8; 12] { 59 | fn from(version: VncVersion) -> Self { 60 | match version { 61 | VncVersion::RFB33 => b"RFB 003.003\n", 62 | VncVersion::RFB37 => b"RFB 003.007\n", 63 | VncVersion::RFB38 => b"RFB 003.008\n", 64 | } 65 | } 66 | } 67 | 68 | impl VncVersion { 69 | pub(crate) async fn read(reader: &mut S) -> Result 70 | where 71 | S: AsyncRead + Unpin, 72 | { 73 | let mut buffer = [0_u8; 12]; 74 | reader.read_exact(&mut buffer).await?; 75 | Ok(buffer.into()) 76 | } 77 | 78 | pub(crate) async fn write(self, writer: &mut S) -> Result<(), VncError> 79 | where 80 | S: AsyncWrite + Unpin, 81 | { 82 | writer 83 | .write_all(&>::into(self)[..]) 84 | .await?; 85 | Ok(()) 86 | } 87 | } 88 | 89 | /// Pixel Format Data Structure according to [RFC6143](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.4) 90 | /// 91 | /// ```text 92 | /// +--------------+--------------+-----------------+ 93 | /// | No. of bytes | Type [Value] | Description | 94 | /// +--------------+--------------+-----------------+ 95 | /// | 1 | U8 | bits-per-pixel | 96 | /// | 1 | U8 | depth | 97 | /// | 1 | U8 | big-endian-flag | 98 | /// | 1 | U8 | true-color-flag | 99 | /// | 2 | U16 | red-max | 100 | /// | 2 | U16 | green-max | 101 | /// | 2 | U16 | blue-max | 102 | /// | 1 | U8 | red-shift | 103 | /// | 1 | U8 | green-shift | 104 | /// | 1 | U8 | blue-shift | 105 | /// | 3 | | padding | 106 | /// +--------------+--------------+-----------------+ 107 | /// ``` 108 | #[derive(Debug, Clone, Copy)] 109 | pub struct PixelFormat { 110 | /// the number of bits used for each pixel value on the wire 111 | /// 112 | /// 8, 16, 32(usually) only 113 | /// 114 | pub bits_per_pixel: u8, 115 | /// Although the depth should 116 | /// 117 | /// be consistent with the bits-per-pixel and the various -max values, 118 | /// 119 | /// clients do not use it when interpreting pixel data. 120 | /// 121 | pub depth: u8, 122 | /// true if multi-byte pixels are interpreted as big endian 123 | /// 124 | pub big_endian_flag: u8, 125 | /// true then the last six items specify how to extract the red, green and blue intensities from the pixel value 126 | /// 127 | pub true_color_flag: u8, 128 | /// the next three always in big-endian order 129 | /// no matter how the `big_endian_flag` is set 130 | /// 131 | pub red_max: u16, 132 | pub green_max: u16, 133 | pub blue_max: u16, 134 | /// the number of shifts needed to get the red value in a pixel to the least significant bit 135 | /// 136 | pub red_shift: u8, 137 | pub green_shift: u8, 138 | pub blue_shift: u8, 139 | _padding_1: u8, 140 | _padding_2: u8, 141 | _padding_3: u8, 142 | } 143 | 144 | impl From for Vec { 145 | fn from(pf: PixelFormat) -> Vec { 146 | vec![ 147 | pf.bits_per_pixel, 148 | pf.depth, 149 | pf.big_endian_flag, 150 | pf.true_color_flag, 151 | (pf.red_max >> 8) as u8, 152 | pf.red_max as u8, 153 | (pf.green_max >> 8) as u8, 154 | pf.green_max as u8, 155 | (pf.blue_max >> 8) as u8, 156 | pf.blue_max as u8, 157 | pf.red_shift, 158 | pf.green_shift, 159 | pf.blue_shift, 160 | pf._padding_1, 161 | pf._padding_2, 162 | pf._padding_3, 163 | ] 164 | } 165 | } 166 | 167 | impl TryFrom<[u8; 16]> for PixelFormat { 168 | type Error = VncError; 169 | 170 | fn try_from(pf: [u8; 16]) -> Result { 171 | let bits_per_pixel = pf[0]; 172 | if bits_per_pixel != 8 && bits_per_pixel != 16 && bits_per_pixel != 32 { 173 | return Err(VncError::WrongPixelFormat); 174 | } 175 | let depth = pf[1]; 176 | let big_endian_flag = pf[2]; 177 | let true_color_flag = pf[3]; 178 | let red_max = u16::from_be_bytes(pf[4..6].try_into().unwrap()); 179 | let green_max = u16::from_be_bytes(pf[6..8].try_into().unwrap()); 180 | let blue_max = u16::from_be_bytes(pf[8..10].try_into().unwrap()); 181 | let red_shift = pf[10]; 182 | let green_shift = pf[11]; 183 | let blue_shift = pf[12]; 184 | let _padding_1 = pf[13]; 185 | let _padding_2 = pf[14]; 186 | let _padding_3 = pf[15]; 187 | Ok(PixelFormat { 188 | bits_per_pixel, 189 | depth, 190 | big_endian_flag, 191 | true_color_flag, 192 | red_max, 193 | green_max, 194 | blue_max, 195 | red_shift, 196 | green_shift, 197 | blue_shift, 198 | _padding_1, 199 | _padding_2, 200 | _padding_3, 201 | }) 202 | } 203 | } 204 | 205 | impl Default for PixelFormat { 206 | // by default the pixel transformed is (a << 24 | r << 16 || g << 8 | b) in le 207 | // which is [b, g, r, a] in network 208 | fn default() -> Self { 209 | Self { 210 | bits_per_pixel: 32, 211 | depth: 24, 212 | big_endian_flag: 0, 213 | true_color_flag: 1, 214 | red_max: 255, 215 | green_max: 255, 216 | blue_max: 255, 217 | red_shift: 16, 218 | green_shift: 8, 219 | blue_shift: 0, 220 | _padding_1: 0, 221 | _padding_2: 0, 222 | _padding_3: 0, 223 | } 224 | } 225 | } 226 | 227 | impl PixelFormat { 228 | // (a << 24 | r << 16 || g << 8 | b) in le 229 | // [b, g, r, a] in network 230 | pub fn bgra() -> PixelFormat { 231 | PixelFormat::default() 232 | } 233 | 234 | // (a << 24 | b << 16 | g << 8 | r) in le 235 | // which is [r, g, b, a] in network 236 | pub fn rgba() -> PixelFormat { 237 | Self { 238 | red_shift: 0, 239 | blue_shift: 16, 240 | ..Default::default() 241 | } 242 | } 243 | 244 | pub(crate) async fn read(reader: &mut S) -> Result 245 | where 246 | S: AsyncRead + Unpin, 247 | { 248 | let mut pixel_buffer = [0_u8; 16]; 249 | reader.read_exact(&mut pixel_buffer).await?; 250 | pixel_buffer.try_into() 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[non_exhaustive] 4 | #[derive(Debug, Error)] 5 | pub enum VncError { 6 | #[error("Auth is required but no password provided")] 7 | NoPassword, 8 | #[error("No VNC encoding selected")] 9 | NoEncoding, 10 | #[error("Unknow VNC security type: {0}")] 11 | InvalidSecurityTyep(u8), 12 | #[error("Wrong password")] 13 | WrongPassword, 14 | #[error("Connect error with unknown reason")] 15 | ConnectError, 16 | #[error("Unknown pixel format")] 17 | WrongPixelFormat, 18 | #[error("Unkonw server message")] 19 | WrongServerMessage, 20 | #[error("Image data cannot be decoded correctly")] 21 | InvalidImageData, 22 | #[error("The VNC client isn't started. Or it is already closed")] 23 | ClientNotRunning, 24 | #[error(transparent)] 25 | IoError(#[from] std::io::Error), 26 | #[error("VNC Error with message: {0}")] 27 | General(String), 28 | } 29 | 30 | impl From> for VncError { 31 | fn from(_value: tokio::sync::mpsc::error::SendError) -> Self { 32 | VncError::General("Channel closed".to_string()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use crate::PixelFormat; 2 | 3 | type ImageData = Vec; 4 | 5 | /// A rect where the image should be updated 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct Rect { 8 | pub x: u16, 9 | pub y: u16, 10 | pub width: u16, 11 | pub height: u16, 12 | } 13 | 14 | /// Resolution format to resize window 15 | #[derive(Debug, Clone)] 16 | pub struct Screen { 17 | pub width: u16, 18 | pub height: u16, 19 | } 20 | 21 | impl From<(u16, u16)> for Screen { 22 | fn from(tuple: (u16, u16)) -> Self { 23 | Self { 24 | width: tuple.0, 25 | height: tuple.1, 26 | } 27 | } 28 | } 29 | 30 | type SrcRect = Rect; 31 | type DstRect = Rect; 32 | 33 | /// Events generated by the [crate::VncClient] 34 | /// 35 | #[non_exhaustive] 36 | #[derive(Debug, Clone)] 37 | pub enum VncEvent { 38 | /// Tell the client how to display the images 39 | /// 40 | /// ```no_compile 41 | /// if let VncEvent::SetResolution(resolution) = event { 42 | /// window.resize(screen.width, screen.height); 43 | /// } 44 | /// ``` 45 | /// 46 | /// Note that this event may be recived multiple times 47 | /// 48 | /// If the [crate::VncEncoding::DesktopSizePseudo] is set 49 | /// 50 | SetResolution(Screen), 51 | /// If the connector doesn't call `set_pixel_format` method 52 | /// 53 | /// The engine will generate a [VncEvent::SetPixelFormat] to let the window know how to render image 54 | /// 55 | SetPixelFormat(PixelFormat), 56 | /// Raw image data in the order followed by informed PixelFormat 57 | /// 58 | RawImage(Rect, ImageData), 59 | /// Copy image data from the second rect to the first 60 | /// 61 | Copy(DstRect, SrcRect), 62 | /// A jpeg image if using Tight encoding, 63 | /// 64 | /// Encoding the bytes with base64 and render it with "", 65 | /// 66 | JpegImage(Rect, ImageData), 67 | 68 | // PngImage(Rect, ImageData), 69 | /// Will be generated if [crate::VncEncoding::CursorPseudo] is set 70 | /// 71 | /// According to [RFC6143, section-7.8.1](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.8.1) 72 | /// 73 | SetCursor(Rect, ImageData), 74 | /// Just ring a bell 75 | /// 76 | Bell, 77 | /// Will be generated everytime the vncserver's clipboarded get updated 78 | /// 79 | /// Note that only Latin-1 character set is allowed 80 | /// 81 | /// According to [RFC6143](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.6.4) 82 | /// 83 | Text(String), 84 | /// If any unexpected error happens in the async process routines 85 | /// This event will propagate the error to the current context 86 | Error(String), 87 | } 88 | 89 | /// X11 keyboard event to notify the server 90 | /// 91 | /// Referring to [RFC6143, section-7.5.4](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.5.4) 92 | /// 93 | #[derive(Debug, Clone)] 94 | pub struct ClientKeyEvent { 95 | pub keycode: u32, 96 | pub down: bool, 97 | } 98 | 99 | impl From<(u32, bool)> for ClientKeyEvent { 100 | fn from(tuple: (u32, bool)) -> Self { 101 | Self { 102 | keycode: tuple.0, 103 | down: tuple.1, 104 | } 105 | } 106 | } 107 | 108 | /// X11 mouse event to notify the server 109 | /// 110 | /// Referring to [RFC6143, seciont-7.5.5](https://www.rfc-editor.org/rfc/rfc6143.html#section-7.5.5) 111 | /// 112 | #[derive(Debug, Clone)] 113 | pub struct ClientMouseEvent { 114 | pub position_x: u16, 115 | pub position_y: u16, 116 | pub bottons: u8, 117 | } 118 | 119 | impl From<(u16, u16, u8)> for ClientMouseEvent { 120 | fn from(tuple: (u16, u16, u8)) -> Self { 121 | Self { 122 | position_x: tuple.0, 123 | position_y: tuple.1, 124 | bottons: tuple.2, 125 | } 126 | } 127 | } 128 | 129 | /// Client-side event which used to ask the engine send some command to the vnc server 130 | /// 131 | #[non_exhaustive] 132 | #[derive(Debug, Clone)] 133 | pub enum X11Event { 134 | /// Require a frame update 135 | /// 136 | Refresh, 137 | /// Key down/up 138 | /// 139 | KeyEvent(ClientKeyEvent), 140 | /// Mouse move/up/down/scroll 141 | /// 142 | PointerEvent(ClientMouseEvent), 143 | /// Send data to the server's clipboard 144 | /// 145 | /// Only Latin-1 character set is allowed 146 | /// 147 | CopyText(String), 148 | } 149 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # VNC-RS 2 | //! 3 | //! ## Description 4 | //! + An async implementation of VNC client side protocol 5 | //! 6 | //! ## Simple example 7 | //! 8 | //! ```no_run 9 | //! use anyhow::{Context, Result}; 10 | //! use minifb::{Window, WindowOptions}; 11 | //! use tokio::{self, net::TcpStream}; 12 | //! use tracing::Level; 13 | //! use vnc::{PixelFormat, Rect, VncConnector, VncEvent, X11Event}; 14 | //! 15 | //! #[tokio::main] 16 | //! async fn main() -> Result<()> { 17 | //! // Create tracing subscriber 18 | //! #[cfg(debug_assertions)] 19 | //! let subscriber = tracing_subscriber::FmtSubscriber::builder() 20 | //! .with_max_level(Level::TRACE) 21 | //! .finish(); 22 | //! #[cfg(not(debug_assertions))] 23 | //! let subscriber = tracing_subscriber::FmtSubscriber::builder() 24 | //! .with_max_level(Level::INFO) 25 | //! .finish(); 26 | //! 27 | //! tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 28 | //! 29 | //! let tcp = TcpStream::connect("127.0.0.1:5900").await?; 30 | //! let vnc = VncConnector::new(tcp) 31 | //! .set_auth_method(async move { Ok("123".to_string()) }) 32 | //! .add_encoding(vnc::VncEncoding::Tight) 33 | //! .add_encoding(vnc::VncEncoding::Zrle) 34 | //! .add_encoding(vnc::VncEncoding::CopyRect) 35 | //! .add_encoding(vnc::VncEncoding::Raw) 36 | //! .allow_shared(true) 37 | //! .set_pixel_format(PixelFormat::bgra()) 38 | //! .build()? 39 | //! .try_start() 40 | //! .await? 41 | //! .finish()?; 42 | //! 43 | //! let mut canvas = CanvasUtils::new()?; 44 | //! 45 | //! let mut now = std::time::Instant::now(); 46 | //! loop { 47 | //! match vnc.poll_event().await { 48 | //! Ok(Some(e)) => { 49 | //! let _ = canvas.hande_vnc_event(e); 50 | //! } 51 | //! Ok(None) => (), 52 | //! Err(e) => { 53 | //! tracing::error!("{}", e.to_string()); 54 | //! break; 55 | //! } 56 | //! } 57 | //! if now.elapsed().as_millis() > 16 { 58 | //! let _ = canvas.flush(); 59 | //! let _ = vnc.input(X11Event::Refresh).await; 60 | //! now = std::time::Instant::now(); 61 | //! } 62 | //! } 63 | //! canvas.close(); 64 | //! let _ = vnc.close().await; 65 | //! Ok(()) 66 | //! } 67 | //! 68 | //! struct CanvasUtils { 69 | //! window: Window, 70 | //! video: Vec, 71 | //! width: u32, 72 | //! height: u32, 73 | //! } 74 | //! 75 | //! impl CanvasUtils { 76 | //! fn new() -> Result { 77 | //! Ok(Self { 78 | //! window: Window::new( 79 | //! "mstsc-rs Remote Desktop in Rust", 80 | //! 800_usize, 81 | //! 600_usize, 82 | //! WindowOptions::default(), 83 | //! ) 84 | //! .with_context(|| "Unable to create window".to_string())?, 85 | //! video: vec![], 86 | //! width: 800, 87 | //! height: 600, 88 | //! }) 89 | //! } 90 | //! 91 | //! fn init(&mut self, width: u32, height: u32) -> Result<()> { 92 | //! let mut window = Window::new( 93 | //! "mstsc-rs Remote Desktop in Rust", 94 | //! width as usize, 95 | //! height as usize, 96 | //! WindowOptions::default(), 97 | //! ) 98 | //! .with_context(|| "Unable to create window")?; 99 | //! window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); 100 | //! self.window = window; 101 | //! self.width = width; 102 | //! self.height = height; 103 | //! self.video.resize(height as usize * width as usize, 0); 104 | //! Ok(()) 105 | //! } 106 | //! 107 | //! fn draw(&mut self, rect: Rect, data: Vec) -> Result<()> { 108 | //! // since we set the PixelFormat as bgra 109 | //! // the pixels must be sent in [blue, green, red, alpha] in the network order 110 | //! 111 | //! let mut s_idx = 0; 112 | //! for y in rect.y..rect.y + rect.height { 113 | //! let mut d_idx = y as usize * self.width as usize + rect.x as usize; 114 | //! 115 | //! for _ in rect.x..rect.x + rect.width { 116 | //! self.video[d_idx] = 117 | //! u32::from_le_bytes(data[s_idx..s_idx + 4].try_into().unwrap()) & 0x00_ff_ff_ff; 118 | //! s_idx += 4; 119 | //! d_idx += 1; 120 | //! } 121 | //! } 122 | //! Ok(()) 123 | //! } 124 | //! 125 | //! fn flush(&mut self) -> Result<()> { 126 | //! self.window 127 | //! .update_with_buffer(&self.video, self.width as usize, self.height as usize) 128 | //! .with_context(|| "Unable to update screen buffer")?; 129 | //! Ok(()) 130 | //! } 131 | //! 132 | //! fn copy(&mut self, dst: Rect, src: Rect) -> Result<()> { 133 | //! println!("Copy"); 134 | //! let mut tmp = vec![0; src.width as usize * src.height as usize]; 135 | //! let mut tmp_idx = 0; 136 | //! for y in 0..src.height as usize { 137 | //! let mut s_idx = (src.y as usize + y) * self.width as usize + src.x as usize; 138 | //! for _ in 0..src.width { 139 | //! tmp[tmp_idx] = self.video[s_idx]; 140 | //! tmp_idx += 1; 141 | //! s_idx += 1; 142 | //! } 143 | //! } 144 | //! tmp_idx = 0; 145 | //! for y in 0..src.height as usize { 146 | //! let mut d_idx = (dst.y as usize + y) * self.width as usize + dst.x as usize; 147 | //! for _ in 0..src.width { 148 | //! self.video[d_idx] = tmp[tmp_idx]; 149 | //! tmp_idx += 1; 150 | //! d_idx += 1; 151 | //! } 152 | //! } 153 | //! Ok(()) 154 | //! } 155 | //! 156 | //! fn close(&self) {} 157 | //! 158 | //! fn hande_vnc_event(&mut self, event: VncEvent) -> Result<()> { 159 | //! match event { 160 | //! VncEvent::SetResolution(screen) => { 161 | //! tracing::info!("Resize {:?}", screen); 162 | //! self.init(screen.width as u32, screen.height as u32)? 163 | //! } 164 | //! VncEvent::RawImage(rect, data) => { 165 | //! self.draw(rect, data)?; 166 | //! } 167 | //! VncEvent::Bell => { 168 | //! tracing::warn!("Bell event got, but ignore it"); 169 | //! } 170 | //! VncEvent::SetPixelFormat(_) => unreachable!(), 171 | //! VncEvent::Copy(dst, src) => { 172 | //! self.copy(dst, src)?; 173 | //! } 174 | //! VncEvent::JpegImage(_rect, _data) => { 175 | //! tracing::warn!("Jpeg event got, but ignore it"); 176 | //! } 177 | //! VncEvent::SetCursor(rect, data) => { 178 | //! if rect.width != 0 { 179 | //! self.draw(rect, data)?; 180 | //! } 181 | //! } 182 | //! VncEvent::Text(string) => { 183 | //! tracing::info!("Got clipboard message {}", string); 184 | //! } 185 | //! _ => unreachable!(), 186 | //! } 187 | //! Ok(()) 188 | //! } 189 | //! } 190 | //! 191 | //! ``` 192 | //! 193 | //! ## License 194 | //! 195 | //! Licensed under either of 196 | //! 197 | //! * Apache License, Version 2.0 198 | //! ([LICENSE-APACHE](LICENSE-APACHE) or ) 199 | //! * MIT license 200 | //! ([LICENSE-MIT](LICENSE-MIT) or ) 201 | //! 202 | //! at your option. 203 | //! 204 | //! ## Contribution 205 | //! 206 | //! Unless you explicitly state otherwise, any contribution intentionally submitted 207 | //! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 208 | //! dual licensed as above, without any additional terms or conditions. 209 | 210 | pub mod client; 211 | mod codec; 212 | pub mod config; 213 | pub mod error; 214 | pub mod event; 215 | 216 | pub use client::VncClient; 217 | pub use client::VncConnector; 218 | pub use config::*; 219 | pub use error::*; 220 | pub use event::*; 221 | --------------------------------------------------------------------------------