commit
e72a718b94
@ -24,6 +24,7 @@ cd ${WEBSOCKIFY} && cargo build --release && cp ./target/release/${WEBSOCKIFY} $
|
||||
[tasks.install-dir]
|
||||
script = '''
|
||||
mkdir -p $INSTALL_PATH
|
||||
cp asserts/* $INSTALL_PATH
|
||||
'''
|
||||
|
||||
[env]
|
||||
|
2
asserts/jquery-3.6.1.min.js
vendored
Normal file
2
asserts/jquery-3.6.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -30,6 +30,7 @@ features = [
|
||||
"Document",
|
||||
"ErrorEvent",
|
||||
"FileReader",
|
||||
"HtmlButtonElement",
|
||||
"HtmlCanvasElement",
|
||||
"ImageData",
|
||||
"Location",
|
||||
|
79
webvnc/asserts/clipboard.css
Normal file
79
webvnc/asserts/clipboard.css
Normal file
@ -0,0 +1,79 @@
|
||||
#canvas {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.clipboardback {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
.clipboard {
|
||||
position: relative;
|
||||
float: right;
|
||||
width: 320px;
|
||||
right: -300px;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
#clipboardbtn {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: fit-content;
|
||||
top: 50%;
|
||||
bottom: 50%;
|
||||
margin: auto;
|
||||
pointer-events: visible;
|
||||
border-bottom-left-radius: 25px;
|
||||
border-top-left-radius: 25px;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
float: left;
|
||||
background: black;
|
||||
color: white;
|
||||
padding-top: 4px;
|
||||
transition: all 0.3s linear;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#clipboardbox {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
float: right;
|
||||
background: white;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-top-left-radius: 15px;
|
||||
}
|
||||
|
||||
.clipboardback-open {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.clipboard-open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#clipboardtxt {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
word-break: break-all;
|
||||
}
|
@ -5,18 +5,6 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>Web Gateway</title>
|
||||
<style type="text/css">
|
||||
.navbar {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background-color: #66ccff;
|
||||
}
|
||||
|
||||
.navbar .navbar-item {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.horizontal-centre {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@ -32,15 +20,11 @@
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: calc(100% - 58px - 40px);
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 58px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@import url("clipboard.css");
|
||||
</style>
|
||||
<script src="jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script type="module" defer>
|
||||
import init from "/webvnc.js";
|
||||
await init();
|
||||
@ -48,5 +32,64 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="horizontal-centre vertical-centre"><canvas id="vnc-canvas" tabIndex=1></canvas></div>
|
||||
</body>
|
||||
<div id="vnc_status" style="position: relative; height: auto;" class="horizontal-centre vertical-centre"></div>
|
||||
<div id="canvas" class="horizontal-centre vertical-centre">
|
||||
<canvas id="vnc-canvas" tabIndex=1></canvas>
|
||||
<button type="button" id="ctrlaltdel" style="display: inline; position:absolute; right: 10px; top: 10px;">Send
|
||||
CtrlAltDel</button>
|
||||
</div>
|
||||
<div class="clipboardback">
|
||||
<div class="clipboard">
|
||||
<button id="clipboardbtn">clipboard</button>
|
||||
<div id="clipboardbox" class="horizontal-centre vertical-centre">
|
||||
<div style="position: relative; top: 50%; transform: translateY(-50%);">
|
||||
<div><textarea id="clipboardtxt" rows="30"></textarea></div>
|
||||
<div><button id="clipboardsend">Send</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript" defer>
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").click(
|
||||
function (e) {
|
||||
e.stopPropagation();
|
||||
if (($("#clipboardbtn")).attr("open1") == 0) {
|
||||
open();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".clipboardback").click(function () {
|
||||
close();
|
||||
})
|
||||
$(".clipboard").click(function () {
|
||||
event.stopPropagation();
|
||||
})
|
||||
function open() {
|
||||
$("#clipboardbtn").attr("open1", 1);
|
||||
$("#clipboardbtn").html(">")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "auto");
|
||||
}
|
||||
|
||||
function close() {
|
||||
$("#clipboardbtn").attr("open1", 0);
|
||||
$("#clipboardbtn").html("clipboard")
|
||||
$(".clipboard").toggleClass("clipboard-open");
|
||||
$(".clipboardback").toggleClass("clipboardback-open");
|
||||
$(".clipboardback").css("pointer-events", "none");
|
||||
}
|
||||
|
||||
function setClipBoard(s) {
|
||||
$("#clipboardtxt").val(s);
|
||||
}
|
||||
|
||||
function getClipBoard() {
|
||||
return $("#clipboardtxt").val();
|
||||
}
|
||||
</script>
|
@ -3,7 +3,9 @@ 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};
|
||||
use web_sys::{
|
||||
CanvasRenderingContext2d, HtmlButtonElement, HtmlCanvasElement, KeyboardEvent, MouseEvent,
|
||||
};
|
||||
struct Canvas {
|
||||
canvas: HtmlCanvasElement,
|
||||
ctx: CanvasRenderingContext2d,
|
||||
@ -67,6 +69,26 @@ impl Canvas {
|
||||
.unwrap();
|
||||
cb.forget();
|
||||
|
||||
let handler = vnc.clone();
|
||||
let ctrl_alt_del_btn = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("ctrlaltdel")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlButtonElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
let ctrl_alt_del = move || {
|
||||
handler.ctrl_alt_del();
|
||||
};
|
||||
let handler = Box::new(ctrl_alt_del) as Box<dyn FnMut()>;
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
ctrl_alt_del_btn.set_onclick(Some(cb.as_ref().unchecked_ref()));
|
||||
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
|
||||
@ -78,7 +100,7 @@ impl Canvas {
|
||||
let handler = vnc.clone();
|
||||
let mouse_move = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::MouseMove);
|
||||
handler.mouse_event(e, MouseEventType::Move);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_move) as Box<dyn FnMut(_)>;
|
||||
@ -93,7 +115,7 @@ impl Canvas {
|
||||
let handler = vnc.clone();
|
||||
let mouse_down = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::MouseDown);
|
||||
handler.mouse_event(e, MouseEventType::Down);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_down) as Box<dyn FnMut(_)>;
|
||||
@ -108,7 +130,7 @@ impl Canvas {
|
||||
let handler = vnc.clone();
|
||||
let mouse_up = move |e: MouseEvent| {
|
||||
e.stop_propagation();
|
||||
handler.mouse_event(e, MouseEventType::MouseUp);
|
||||
handler.mouse_event(e, MouseEventType::Up);
|
||||
};
|
||||
|
||||
let handler = Box::new(mouse_up) as Box<dyn FnMut(_)>;
|
||||
@ -171,6 +193,10 @@ impl Canvas {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
self.ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CanvasUtils {
|
||||
@ -203,4 +229,8 @@ impl CanvasUtils {
|
||||
pub fn draw(&self, ri: &ImageData) {
|
||||
self.inner.as_ref().draw(ri);
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.as_ref().close()
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use canvas::CanvasUtils;
|
||||
use vnc::Vnc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{ErrorEvent, MessageEvent, WebSocket};
|
||||
use web_sys::{ErrorEvent, HtmlButtonElement, MessageEvent, WebSocket};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
@ -17,10 +17,45 @@ macro_rules! console_log {
|
||||
extern "C" {
|
||||
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||
// fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||
fn cancelInterval(token: f64);
|
||||
fn clearInterval(token: f64);
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(s: &str);
|
||||
pub fn prompt(s: &str) -> String;
|
||||
pub fn setClipBoard(s: String);
|
||||
pub fn getClipBoard() -> String;
|
||||
}
|
||||
|
||||
static mut REFRESHER: Option<Interval> = None;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Interval {
|
||||
_closure: Closure<dyn FnMut()>,
|
||||
token: f64,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
pub fn new<F: 'static>(millis: u32, f: F) -> Interval
|
||||
where
|
||||
F: FnMut(),
|
||||
{
|
||||
// Construct a new closure.
|
||||
let closure = Closure::new(f);
|
||||
// Pass the closure to JS, to run every n milliseconds.
|
||||
let token = setInterval(&closure, millis);
|
||||
|
||||
Interval {
|
||||
_closure: closure,
|
||||
token,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the Interval is destroyed, cancel its `setInterval` timer.
|
||||
impl Drop for Interval {
|
||||
fn drop(&mut self) {
|
||||
console_log!("interval dropped");
|
||||
clearInterval(self.token);
|
||||
}
|
||||
}
|
||||
|
||||
fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc, canvas: &CanvasUtils) {
|
||||
@ -33,7 +68,10 @@ fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc, canvas: &CanvasUtils) {
|
||||
}
|
||||
vnc::VncOutput::WsBuf(buf) => match ws.send_with_u8_array(buf) {
|
||||
Ok(_) => {}
|
||||
Err(err) => console_log!("error sending message: {:?}", err),
|
||||
Err(err) => {
|
||||
console_log!("error sending message: {:?}", err);
|
||||
vnc_close_handle(vnc, canvas);
|
||||
}
|
||||
},
|
||||
vnc::VncOutput::RequirePassword => {
|
||||
let pwd = prompt("Please input the password");
|
||||
@ -59,27 +97,36 @@ fn vnc_out_handler(ws: &WebSocket, vnc: &Vnc, canvas: &CanvasUtils) {
|
||||
vnc_out_handler(&ws_cloned, &vnc_cloned, &canvas_cloned);
|
||||
};
|
||||
|
||||
let handler = Box::new(refresh) as Box<dyn FnMut()>;
|
||||
let refersher = Interval::new(20, refresh);
|
||||
|
||||
let cb = Closure::wrap(handler);
|
||||
|
||||
setInterval(&cb, 20);
|
||||
cb.forget();
|
||||
unsafe {
|
||||
REFRESHER = Some(refersher);
|
||||
}
|
||||
}
|
||||
vnc::VncOutput::SetClipboard(text) => {
|
||||
setClipBoard(text.to_owned());
|
||||
// ConsoleService::log(&self.error_msg);
|
||||
}
|
||||
// vnc::VncOutput::SetClipboard(text) => {
|
||||
// self.clipboard
|
||||
// .borrow_mut()
|
||||
// .as_mut()
|
||||
// .unwrap()
|
||||
// .send_message(components::clipboard::ClipboardMsg::UpdateClipboard(text));
|
||||
// // ConsoleService::log(&self.error_msg);
|
||||
// }
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vnc_close_handle(vnc: &Vnc, canvas: &CanvasUtils) {
|
||||
vnc.close();
|
||||
unsafe {
|
||||
REFRESHER.take();
|
||||
}
|
||||
canvas.close();
|
||||
let status = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("vnc_status")
|
||||
.unwrap();
|
||||
status.set_text_content(Some("Disconnected"));
|
||||
}
|
||||
|
||||
fn start_websocket() -> Result<(), JsValue> {
|
||||
// connect
|
||||
let url = format!(
|
||||
@ -100,16 +147,35 @@ fn start_websocket() -> Result<(), JsValue> {
|
||||
let canvas = CanvasUtils::new();
|
||||
let vnc = Vnc::new();
|
||||
|
||||
let clipboard = web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
.unwrap()
|
||||
.get_element_by_id("clipboardsend")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlButtonElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
let vnc_cloned = vnc.clone();
|
||||
let onclickcb = Closure::<dyn FnMut()>::new(move || {
|
||||
console_log!("Send {:?}", getClipBoard());
|
||||
vnc_cloned.set_clipboard(&getClipBoard());
|
||||
});
|
||||
clipboard.set_onclick(Some(onclickcb.as_ref().unchecked_ref()));
|
||||
onclickcb.forget();
|
||||
|
||||
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
// on message
|
||||
let cloned_ws = ws.clone();
|
||||
let vnc_cloned = vnc.clone();
|
||||
let canvas_cloned = canvas.clone();
|
||||
|
||||
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
||||
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
|
||||
let array = js_sys::Uint8Array::new(&abuf);
|
||||
// let mut canvas_ctx = None;
|
||||
vnc.do_input(array.to_vec());
|
||||
vnc_out_handler(&cloned_ws, &vnc, &canvas);
|
||||
vnc_cloned.do_input(array.to_vec());
|
||||
vnc_out_handler(&cloned_ws, &vnc_cloned, &canvas_cloned);
|
||||
} else {
|
||||
console_log!("message event, received Unknown: {:?}", e.data());
|
||||
}
|
||||
@ -134,6 +200,7 @@ fn start_websocket() -> Result<(), JsValue> {
|
||||
|
||||
let onclose_callback = Closure::<dyn FnMut()>::new(move || {
|
||||
console_log!("socket close");
|
||||
vnc_close_handle(&vnc, &canvas);
|
||||
});
|
||||
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||
onclose_callback.forget();
|
||||
|
@ -4,9 +4,9 @@ mod x11cursor;
|
||||
mod x11keyboard;
|
||||
|
||||
pub enum MouseEventType {
|
||||
MouseDown,
|
||||
MouseUp,
|
||||
MouseMove,
|
||||
Down,
|
||||
Up,
|
||||
Move,
|
||||
}
|
||||
|
||||
use std::{rc::Rc, sync::Mutex};
|
||||
@ -67,6 +67,14 @@ impl Vnc {
|
||||
pub fn mouse_event(&self, mouse: web_sys::MouseEvent, et: MouseEventType) {
|
||||
self.inner.lock().unwrap().mouse_event(mouse, et);
|
||||
}
|
||||
|
||||
pub fn ctrl_alt_del(&self) {
|
||||
self.inner.lock().unwrap().ctrl_alt_del();
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.inner.lock().unwrap().close();
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Vnc {
|
||||
|
@ -166,6 +166,10 @@ impl Vnc {
|
||||
}
|
||||
|
||||
pub fn set_clipboard(&mut self, text: &str) {
|
||||
if self.state != VncState::Connected {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_client_cut_text(text);
|
||||
}
|
||||
|
||||
@ -177,6 +181,15 @@ impl Vnc {
|
||||
self.send_key_event(key, down);
|
||||
}
|
||||
|
||||
pub fn ctrl_alt_del(&mut self) {
|
||||
self.send_key_event(x11keyboard::XK_Control_L, true);
|
||||
self.send_key_event(x11keyboard::XK_Alt_L, true);
|
||||
self.send_key_event(x11keyboard::XK_Delete, true);
|
||||
self.send_key_event(x11keyboard::XK_Control_L, false);
|
||||
self.send_key_event(x11keyboard::XK_Alt_L, false);
|
||||
self.send_key_event(x11keyboard::XK_Delete, false);
|
||||
}
|
||||
|
||||
pub fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType) {
|
||||
if self.state != VncState::Connected {
|
||||
return;
|
||||
@ -186,6 +199,10 @@ impl Vnc {
|
||||
}
|
||||
|
||||
pub fn require_frame(&mut self, incremental: u8) {
|
||||
if self.state != VncState::Connected {
|
||||
return;
|
||||
}
|
||||
|
||||
if 0 == incremental {
|
||||
// first frame
|
||||
// set the client encoding
|
||||
@ -195,6 +212,10 @@ impl Vnc {
|
||||
self.framebuffer_update_request(incremental)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.state = VncState::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -22,16 +22,16 @@ impl MouseUtils {
|
||||
let mask: u8 = (event.button() << 1).try_into().unwrap_or(0);
|
||||
|
||||
match et {
|
||||
MouseEventType::MouseDown => {
|
||||
MouseEventType::Down => {
|
||||
self.down = true;
|
||||
self.mask = self.down as u8 | mask;
|
||||
}
|
||||
MouseEventType::MouseUp => {
|
||||
MouseEventType::Up => {
|
||||
self.down = false;
|
||||
self.mask = self.down as u8 & (!mask);
|
||||
}
|
||||
|
||||
MouseEventType::MouseMove => {}
|
||||
MouseEventType::Move => {}
|
||||
}
|
||||
(x, y, self.mask)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user