Reduce the api call to get canvas

This commit is contained in:
Jovi Hsu 2022-09-28 00:55:39 +00:00
parent e651ee1b53
commit 1ba123dd2b
4 changed files with 235 additions and 194 deletions

206
webvnc/src/canvas.rs Normal file
View File

@ -0,0 +1,206 @@
use std::rc::Rc;
use crate::vnc::{ImageData, MouseEventType, Vnc};
use wasm_bindgen::prelude::*;
use wasm_bindgen::{Clamped, JsCast};
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, KeyboardEvent, MouseEvent};
struct Canvas {
canvas: HtmlCanvasElement,
ctx: CanvasRenderingContext2d,
}
impl Canvas {
fn new() -> Self {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("vnc-canvas").unwrap();
let canvas: HtmlCanvasElement = canvas
.dyn_into::<HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
let ctx = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.unwrap();
Self { canvas, ctx }
}
fn set_resolution(&self, width: u32, height: u32) {
// set hight & width
self.canvas.set_height(height);
self.canvas.set_width(width);
self.ctx.rect(0_f64, 0_f64, width as f64, height as f64);
self.ctx.fill();
}
fn bind(&self, vnc: &Vnc) {
let handler = vnc.clone();
let key_down = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, true);
};
let handler = Box::new(key_down) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("keydown", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let key_up = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, false);
};
let handler = Box::new(key_up) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("keyup", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
// On a conventional mouse, buttons 1, 2, and 3 correspond to the left,
// middle, and right buttons on the mouse. On a wheel mouse, each step
// of the wheel upwards is represented by a press and release of button
// 4, and each step downwards is represented by a press and release of
// button 5.
// to do:
// calculate relation position
let handler = vnc.clone();
let mouse_move = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseMove);
};
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let mouse_down = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseDown);
};
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("mousedown", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let mouse_up = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseUp);
};
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("mouseup", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let get_context_menu = move |e: MouseEvent| {
e.prevent_default();
e.stop_propagation();
};
let handler = Box::new(get_context_menu) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
self.canvas
.add_event_listener_with_callback("contextmenu", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
}
fn draw(&self, ri: &ImageData) {
match ri.type_ {
1 => {
//copy
let sx = (ri.data[0] as u16) << 8 | ri.data[1] as u16;
let sy = (ri.data[2] as u16) << 8 | ri.data[3] as u16;
let _ = self
.ctx
.draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
&self.canvas,
sx as f64,
sy as f64,
ri.width as f64,
ri.height as f64,
ri.x as f64,
ri.y as f64,
ri.width as f64,
ri.height as f64,
);
}
_ => {
let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
Clamped(&ri.data),
ri.width as u32,
ri.height as u32,
)
.unwrap();
// ConsoleService::log(&format!(
// "renderring at ({}, {}), width {}, height {}",
// cr.x, cr.y, cr.width, cr.height
// ));
let _ = self.ctx.put_image_data(&data, ri.x as f64, ri.y as f64);
}
}
}
}
pub struct CanvasUtils {
inner: Rc<Canvas>,
}
impl Clone for CanvasUtils {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl CanvasUtils {
pub fn new() -> Self {
Self {
inner: Rc::new(Canvas::new()),
}
}
pub fn init(&self, width: u32, height: u32) {
self.inner.as_ref().set_resolution(width, height);
}
pub fn bind(&self, vnc: &Vnc) {
self.inner.as_ref().bind(vnc);
}
pub fn draw(&self, ri: &ImageData) {
self.inner.as_ref().draw(ri);
}
}

View File

@ -1,12 +1,12 @@
mod canvas;
mod utils;
mod vnc;
use vnc::{MouseEventType, Vnc};
use canvas::CanvasUtils;
use vnc::Vnc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::{Clamped, JsCast};
use web_sys::{
ErrorEvent, HtmlCanvasElement, ImageData, KeyboardEvent, MessageEvent, MouseEvent, WebSocket,
};
use wasm_bindgen::JsCast;
use web_sys::{ErrorEvent, MessageEvent, WebSocket};
#[macro_export]
macro_rules! console_log {
@ -23,140 +23,7 @@ extern "C" {
pub fn prompt(s: &str) -> String;
}
fn bind_mouse_and_key(vnc: &Vnc, canvas: &HtmlCanvasElement) {
let _window = web_sys::window().unwrap();
let handler = vnc.clone();
let key_down = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, true);
};
let handler = Box::new(key_down) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("keydown", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let key_up = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, false);
};
let handler = Box::new(key_up) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("keyup", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
// On a conventional mouse, buttons 1, 2, and 3 correspond to the left,
// middle, and right buttons on the mouse. On a wheel mouse, each step
// of the wheel upwards is represented by a press and release of button
// 4, and each step downwards is represented by a press and release of
// button 5.
// to do:
// calculate relation position
let handler = vnc.clone();
let mouse_move = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseMove);
};
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let mouse_down = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseDown);
};
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("mousedown", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let handler = vnc.clone();
let mouse_up = move |e: MouseEvent| {
e.stop_propagation();
handler.mouse_event(e, MouseEventType::MouseUp);
};
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("mouseup", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
let get_context_menu = move |e: MouseEvent| {
e.prevent_default();
e.stop_propagation();
};
let handler = Box::new(get_context_menu) as Box<dyn FnMut(_)>;
let cb = Closure::wrap(handler);
canvas
.add_event_listener_with_callback("contextmenu", cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
}
fn find_canvas() -> HtmlCanvasElement {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("vnc-canvas").unwrap();
let canvas: web_sys::HtmlCanvasElement = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
canvas
}
fn set_canvas(vnc: &Vnc, x: u16, y: u16) {
let canvas = find_canvas();
// set hight & width
canvas.set_height(y as u32);
canvas.set_width(x as u32);
// bind keyboard & mouse
bind_mouse_and_key(vnc, &canvas);
let ctx = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
ctx.rect(0 as f64, 0 as f64, x as f64, y as f64);
ctx.fill();
}
fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc) {
fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc, canvas: &CanvasUtils) {
let out = vnc.get_output();
if !out.is_empty() {
for ref o in out {
@ -171,63 +38,25 @@ fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc) {
vnc::VncOutput::RequirePassword => {
let pwd = prompt("Please input the password");
vnc.set_credential(&pwd);
vnc_out_handler(ws, vnc);
vnc_out_handler(ws, vnc, canvas);
}
vnc::VncOutput::RenderCanvas(cr) => {
let canvas = find_canvas();
let ctx = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
match cr.type_ {
1 => {
//copy
let sx = (cr.data[0] as u16) << 8 | cr.data[1] as u16;
let sy = (cr.data[2] as u16) << 8 | cr.data[3] as u16;
let _ = ctx.
draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
&canvas,
sx as f64,
sy as f64,
cr.width as f64,
cr.height as f64,
cr.x as f64,
cr.y as f64,
cr.width as f64,
cr.height as f64
);
vnc::VncOutput::RenderImage(ri) => {
canvas.draw(ri);
}
_ => {
let data = ImageData::new_with_u8_clamped_array_and_sh(
Clamped(&cr.data),
cr.width as u32,
cr.height as u32,
)
.unwrap();
// ConsoleService::log(&format!(
// "renderring at ({}, {}), width {}, height {}",
// cr.x, cr.y, cr.width, cr.height
// ));
let _ = ctx.put_image_data(&data, cr.x as f64, cr.y as f64);
}
}
}
vnc::VncOutput::SetCanvas(x, y) => {
set_canvas(vnc, *x, *y);
vnc::VncOutput::SetResolution(x, y) => {
canvas.init(*x as u32, *y as u32);
canvas.bind(vnc);
vnc.require_frame(0);
vnc_out_handler(ws, vnc);
vnc_out_handler(ws, vnc, canvas);
let vnc_cloned = vnc.clone();
let ws_cloned = ws.clone();
let canvas_cloned = canvas.clone();
// set a interval for fps enhance
let refresh = move || {
vnc_cloned.require_frame(1);
vnc_out_handler(&ws_cloned, &vnc_cloned);
vnc_out_handler(&ws_cloned, &vnc_cloned, &canvas_cloned);
};
let handler = Box::new(refresh) as Box<dyn FnMut()>;
@ -268,10 +97,10 @@ fn start_websocket() -> Result<(), JsValue> {
host = web_sys::window().unwrap().location().host()?
);
let ws = WebSocket::new_with_str(&url, "binary")?;
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
let canvas = CanvasUtils::new();
let vnc = Vnc::new();
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
// on message
let cloned_ws = ws.clone();
@ -280,7 +109,7 @@ fn start_websocket() -> Result<(), JsValue> {
let array = js_sys::Uint8Array::new(&abuf);
// let mut canvas_ctx = None;
vnc.do_input(array.to_vec());
vnc_out_handler(&cloned_ws, &vnc);
vnc_out_handler(&cloned_ws, &vnc, &canvas);
} else {
console_log!("message event, received Unknown: {:?}", e.data());
}
@ -303,6 +132,12 @@ fn start_websocket() -> Result<(), JsValue> {
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
let onclose_callback = Closure::<dyn FnMut()>::new(move || {
console_log!("socket close");
});
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
onclose_callback.forget();
Ok(())
}

View File

@ -11,7 +11,7 @@ pub enum MouseEventType {
use std::{rc::Rc, sync::Mutex};
pub struct CanvasData {
pub struct ImageData {
pub type_: u32,
pub x: u16,
pub y: u16,
@ -24,8 +24,8 @@ pub enum VncOutput {
WsBuf(Vec<u8>),
Err(String),
RequirePassword,
SetCanvas(u16, u16),
RenderCanvas(CanvasData),
SetResolution(u16, u16),
RenderImage(ImageData),
SetClipboard(String),
}

View File

@ -455,7 +455,7 @@ impl Vnc {
self.state = VncState::Connected;
self.require = 1; // any message from sever will be handled
self.outs
.push(VncOutput::SetCanvas(self.width, self.height));
.push(VncOutput::SetResolution(self.width, self.height));
}
fn handle_server_message(&mut self) {
@ -567,7 +567,7 @@ impl Vnc {
}
_ => unimplemented!(),
}
self.outs.push(VncOutput::RenderCanvas(CanvasData {
self.outs.push(VncOutput::RenderImage(ImageData {
type_: rect.encoding_type,
x: rect.x,
y: rect.y,