Add a simple clipboard

This commit is contained in:
Jovi Hsu 2021-11-27 16:17:07 +08:00
parent ac33b294c2
commit 6ac6a0a522
6 changed files with 154 additions and 34 deletions

View File

@ -0,0 +1,69 @@
use yew::prelude::*;
use crate::utils::WeakComponentLink;
pub enum ClipboardMsg {
UpdateClipboard(String),
SendClipboard,
}
pub struct Clipboard {
link: ComponentLink<Self>,
onsubmit: Callback<String>,
text: String,
}
// Props
#[derive(Clone, PartialEq, Properties)]
pub struct ClipboardProps {
#[prop_or_default]
pub weak_link: WeakComponentLink<Clipboard>,
pub onsubmit: Callback<String>,
}
impl Component for Clipboard {
type Message = ClipboardMsg;
type Properties = ClipboardProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
props.weak_link.borrow_mut().replace(link.clone());
Clipboard {
link,
onsubmit: props.onsubmit,
text: String::new(),
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
ClipboardMsg::UpdateClipboard(text) => {
self.text = text;
}
ClipboardMsg::SendClipboard => {
self.onsubmit.emit(self.text.clone());
self.text.clear();
}
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
let update_clipboard = self.link.callback(|e: ChangeData| match e {
ChangeData::Value(v) => ClipboardMsg::UpdateClipboard(v),
_ => panic!("unexpected message"),
});
let set_clipboard = self.link.callback(|_| ClipboardMsg::SendClipboard);
html! {
<>
<textarea rows="5" cols="60" id="clipboard" onchange=update_clipboard value=self.text.clone()/>
<br/>
<button id="clipboard-send" onclick=set_clipboard> {"Send to peer"} </button>
<br/>
</>
}
}
}

View File

@ -1,4 +1,5 @@
pub mod auth;
pub mod clipboard;
pub mod host;
pub mod input;
pub mod ws;

View File

@ -26,7 +26,7 @@ pub struct PageRemote {
fetch_task: Option<FetchTask>,
connected: bool,
handler: ProtocalHandler<VncHandler>,
ws_link: WeakComponentLink<components::ws::WebsocketCtx>,
websocket: WeakComponentLink<components::ws::WebsocketCtx>,
request_username: bool,
request_password: bool,
username: String,
@ -34,6 +34,7 @@ pub struct PageRemote {
canvas: NodeRef,
canvas_ctx: Option<CanvasRenderingContext2d>,
interval: Option<Interval>,
clipboard: WeakComponentLink<components::clipboard::Clipboard>,
}
#[derive(Clone, PartialEq, Properties)]
@ -47,6 +48,7 @@ pub enum RemoteMsg {
Send(Vec<u8>),
UpdateUsername(String),
UpdatePassword(String),
UpdateClipboard(String),
SendCredential,
RequireFrame(u8),
}
@ -63,7 +65,7 @@ impl Component for PageRemote {
fetch_task: None,
connected: false,
handler: ProtocalHandler::new(),
ws_link: WeakComponentLink::default(),
websocket: WeakComponentLink::default(),
request_username: false,
request_password: false,
username: String::from(""),
@ -71,6 +73,7 @@ impl Component for PageRemote {
canvas: NodeRef::default(),
canvas_ctx: None,
interval: None,
clipboard: WeakComponentLink::default(),
}
}
@ -129,7 +132,7 @@ impl Component for PageRemote {
self.protocal_out_handler()
}
RemoteMsg::Send(v) => {
self.ws_link
self.websocket
.borrow()
.as_ref()
.unwrap()
@ -160,6 +163,14 @@ impl Component for PageRemote {
}
self.protocal_out_handler()
}
RemoteMsg::UpdateClipboard(clipboard) => {
if clipboard.len() > 0 {
self.handler.set_clipboard(&clipboard);
self.protocal_out_handler()
} else {
false
}
}
}
}
@ -178,7 +189,9 @@ impl Component for PageRemote {
}
} else {
let recv_msg = self.link.callback(RemoteMsg::Recv);
let ws_link = &self.ws_link;
let clipboard_update = self.link.callback(RemoteMsg::UpdateClipboard);
let websocket = &self.websocket;
let clipboard = &self.clipboard;
html! {
<>
<div class="horizontal-centre vertical-centre">
@ -186,8 +199,10 @@ impl Component for PageRemote {
{self.password_view()}
{self.button_connect_view()}
<components::ws::WebsocketCtx
weak_link=ws_link onrecv=recv_msg/>
weak_link=websocket onrecv=recv_msg/>
<canvas id="remote-canvas" ref=self.canvas.clone()></canvas>
<components::clipboard::Clipboard
weak_link=clipboard onsubmit=clipboard_update/>
{self.error_msg.clone()}
</div>
</>
@ -212,7 +227,7 @@ impl PageRemote {
match o {
ProtocalHandlerOutput::Err(err) => {
self.error_msg = err.clone();
self.ws_link
self.websocket
.borrow_mut()
.as_mut()
.unwrap()
@ -220,8 +235,10 @@ impl PageRemote {
should_render = true;
}
ProtocalHandlerOutput::WsBuf(out) => {
if out.len() > 0 {
self.link.send_message(RemoteMsg::Send(out));
}
}
ProtocalHandlerOutput::RequirePassword => {
self.request_password = true;
should_render = true;
@ -273,9 +290,11 @@ impl PageRemote {
should_render = true;
}
ProtocalHandlerOutput::SetClipboard(text) => {
self.error_msg = format!("Clipboard get {}", text);
ConsoleService::log(&self.error_msg);
should_render = true;
self.clipboard.borrow_mut().as_mut().unwrap().send_message(
components::clipboard::ClipboardMsg::UpdateClipboard(text),
);
// ConsoleService::log(&self.error_msg);
should_render = false;
}
_ => unimplemented!(),
}
@ -330,6 +349,7 @@ impl PageRemote {
let window = web_sys::window().unwrap();
let handler = self.handler.clone();
let key_down = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, true);
};
@ -345,6 +365,7 @@ impl PageRemote {
let handler = self.handler.clone();
let key_up = move |e: KeyboardEvent| {
e.prevent_default();
e.stop_propagation();
handler.key_press(e, false);
};

View File

@ -68,6 +68,10 @@ where
.set_credential(username, password);
}
pub fn set_clipboard(&mut self, text: &str) {
self.inner.as_ref().lock().unwrap().set_clipboard(text);
}
pub fn set_resolution(&self, width: u16, height: u16) {
self.inner
.as_ref()
@ -100,6 +104,7 @@ pub trait ProtocalImpl {
fn do_input(&mut self, input: Vec<u8>);
fn get_output(&mut self) -> Vec<ProtocalHandlerOutput>;
fn set_credential(&mut self, username: &str, password: &str);
fn set_clipboard(&mut self, text: &str);
fn set_resolution(&mut self, width: u16, height: u16);
fn key_press(&mut self, key: web_sys::KeyboardEvent, down: bool);
fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType);
@ -233,18 +238,18 @@ impl<'a> StreamWriter<'a> {
self.write_u32(b as u32);
}
pub fn write_string_with_len(&mut self, s: &str) {
pub fn write_string(&mut self, s: &str) {
self.inner.extend_from_slice(s.as_bytes());
}
pub fn write_string_l16(&mut self, s: &str) {
self.write_u16(s.len() as u16);
self.write_string_with_len(s);
self.write_string(s);
}
pub fn write_string_l32(&mut self, s: &str) {
self.write_u32(s.len() as u32);
self.write_string_with_len(s);
self.write_string(s);
}
pub fn write_slice(&mut self, s: &[u8]) {

View File

@ -123,6 +123,7 @@ impl ProtocalImpl for VncHandler {
}
fn get_output(&mut self) -> Vec<ProtocalHandlerOutput> {
if let ServerMessage::None = self.msg_handling {
let mut out = Vec::with_capacity(self.outs.len());
// ConsoleService::log(&format!("Get {} output", self.outs.len()));
for o in self.outs.drain(..) {
@ -132,7 +133,10 @@ impl ProtocalImpl for VncHandler {
out.push(ProtocalHandlerOutput::WsBuf(self.outbuf.clone()));
self.outbuf.clear();
}
out
return out;
} else {
return Vec::new();
}
}
fn set_credential(&mut self, _username: &str, password: &str) {
@ -162,6 +166,10 @@ impl ProtocalImpl for VncHandler {
self.require = 4; // the auth result message length
}
fn set_clipboard(&mut self, text: &str) {
self.send_client_cut_text(text);
}
fn set_resolution(&mut self, _width: u16, _height: u16) {
// VNC client doen't support resolution change
}
@ -171,20 +179,16 @@ impl ProtocalImpl for VncHandler {
return;
}
let key = x11keyboard::KeyboardUtils::get_keysym(key);
if let ServerMessage::None = self.msg_handling {
self.send_key_event(key, down);
}
}
fn mouse_event(&mut self, mouse: web_sys::MouseEvent, et: MouseEventType) {
if self.state != VncState::Connected {
return;
}
let (x, y, mask) = self.mouse.get_mouse_sym(mouse, et);
if let ServerMessage::None = self.msg_handling {
self.send_pointer_event(x, y, mask);
}
}
fn require_frame(&mut self, incremental: u8) {
if 0 == incremental {
@ -324,7 +328,29 @@ impl VncHandler {
sw.write_u16(x); // x
sw.write_u16(y); // y
ConsoleService::log(&format!("send mouse event {:x?} {:x?} {:#08b}", x, y, mask));
// ConsoleService::log(&format!("send mouse event {:x?} {:x?} {:#08b}", x, y, mask));
self.outbuf.extend_from_slice(&out);
}
// +--------------+--------------+--------------+
// | No. of bytes | Type [Value] | Description |
// +--------------+--------------+--------------+
// | 1 | U8 [6] | message-type |
// | 3 | | padding |
// | 4 | U32 | length |
// | length | U8 array | text |
// +--------------+--------------+--------------+
fn send_client_cut_text(&mut self, text: &str) {
let mut out = Vec::with_capacity(10);
let mut sw = StreamWriter::new(&mut out);
let len: u32 = text.len().try_into().unwrap_or(0);
sw.write_u8(6); // message-type
sw.write_u8(0); // padding
sw.write_u16(0); // padding
sw.write_u32(len); // length
sw.write_string(text); // text
// ConsoleService::log(&format!("send client cut text {:?}", len));
self.outbuf.extend_from_slice(&out);
}
@ -546,8 +572,6 @@ impl VncHandler {
self.require = 12; // the length of the next rectangle hdr
}
}
// ConsoleService::log(&format!("{} rects left", self.num_rects_left));
}
// Currently there is little or no support for colour maps. Some preliminary work was done

View File

@ -3,7 +3,7 @@
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use yew::services::ConsoleService;
// referring:
// https://github.com/AltF02/x11-rs/blob/master/src/keysym.rs
@ -1347,7 +1347,7 @@ impl KeyboardUtils {
let capslock = event.get_modifier_state("CapsLock");
let upper = capslock ^ shift;
let which = event.which();
ConsoleService::log(&format!("which {}, shift {}", which, shift));
// ConsoleService::log(&format!("which {}, shift {}", which, shift));
match which {
8_u32 => {
// Backspace
@ -1359,7 +1359,7 @@ impl KeyboardUtils {
}
13_u32 => {
// Enter
XK_Linefeed
XK_Return
}
16_u32 => {
// ShiftLeft