mirror of
https://github.com/netbirdio/IronRDP.git
synced 2026-05-22 18:43:12 -07:00
refactor(web): rework clipboard API (#764)
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::js_sys;
|
||||
|
||||
pub trait ClipboardTransaction {
|
||||
type ClipboardContent: ClipboardContent;
|
||||
pub trait ClipboardData {
|
||||
type Item: ClipboardItem;
|
||||
|
||||
fn init() -> Self;
|
||||
fn add_content(&mut self, content: Self::ClipboardContent);
|
||||
fn is_empty(&self) -> bool;
|
||||
fn contents(&self) -> js_sys::Array;
|
||||
fn add_text(&mut self, mime_type: &str, text: &str);
|
||||
fn add_binary(&mut self, mime_type: &str, binary: &[u8]);
|
||||
fn items(&self) -> &[Self::Item];
|
||||
}
|
||||
|
||||
pub trait ClipboardContent {
|
||||
fn new_text(mime_type: &str, text: &str) -> Self;
|
||||
fn new_binary(mime_type: &str, binary: &[u8]) -> Self;
|
||||
pub trait ClipboardItem {
|
||||
fn mime_type(&self) -> &str;
|
||||
fn value(&self) -> JsValue;
|
||||
fn value(&self) -> impl Into<JsValue>;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ mod extension;
|
||||
mod input;
|
||||
mod session;
|
||||
|
||||
pub use clipboard::{ClipboardContent, ClipboardTransaction};
|
||||
pub use clipboard::{ClipboardData, ClipboardItem};
|
||||
pub use cursor::CursorStyle;
|
||||
pub use desktop_size::DesktopSize;
|
||||
pub use error::{IronError, IronErrorKind};
|
||||
@@ -20,8 +20,8 @@ pub trait RemoteDesktopApi {
|
||||
type SessionTerminationInfo: SessionTerminationInfo;
|
||||
type DeviceEvent: DeviceEvent;
|
||||
type InputTransaction: InputTransaction;
|
||||
type ClipboardTransaction: ClipboardTransaction;
|
||||
type ClipboardContent: ClipboardContent;
|
||||
type ClipboardData: ClipboardData;
|
||||
type ClipboardItem: ClipboardItem;
|
||||
type Error: IronError;
|
||||
|
||||
/// Called before the logger is set.
|
||||
@@ -38,8 +38,8 @@ macro_rules! export {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{js_sys, HtmlCanvasElement};
|
||||
use $crate::{
|
||||
ClipboardContent as _, ClipboardTransaction as _, DeviceEvent as _, InputTransaction as _,
|
||||
IronError as _, RemoteDesktopApi, Session as _, SessionBuilder as _, SessionTerminationInfo as _,
|
||||
ClipboardData as _, ClipboardItem as _, DeviceEvent as _, InputTransaction as _, IronError as _,
|
||||
RemoteDesktopApi, Session as _, SessionBuilder as _, SessionTerminationInfo as _,
|
||||
};
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -52,6 +52,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct DeviceEvent(<$api as RemoteDesktopApi>::DeviceEvent);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::DeviceEvent> for DeviceEvent {
|
||||
fn from(value: <$api as RemoteDesktopApi>::DeviceEvent) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceEvent {
|
||||
pub fn mouse_button_pressed(button: u8) -> Self {
|
||||
@@ -101,6 +107,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct InputTransaction(<$api as RemoteDesktopApi>::InputTransaction);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::InputTransaction> for InputTransaction {
|
||||
fn from(value: <$api as RemoteDesktopApi>::InputTransaction) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl InputTransaction {
|
||||
pub fn init() -> Self {
|
||||
@@ -115,6 +127,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct IronError(<$api as RemoteDesktopApi>::Error);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::Error> for IronError {
|
||||
fn from(value: <$api as RemoteDesktopApi>::Error) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl IronError {
|
||||
pub fn backtrace(&self) -> String {
|
||||
@@ -129,6 +147,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct Session(<$api as RemoteDesktopApi>::Session);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::Session> for Session {
|
||||
fn from(value: <$api as RemoteDesktopApi>::Session) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Session {
|
||||
pub async fn run(&self) -> Result<SessionTerminationInfo, IronError> {
|
||||
@@ -163,7 +187,7 @@ macro_rules! export {
|
||||
self.0.shutdown().map_err(IronError)
|
||||
}
|
||||
|
||||
pub async fn on_clipboard_paste(&self, content: ClipboardTransaction) -> Result<(), IronError> {
|
||||
pub async fn on_clipboard_paste(&self, content: ClipboardData) -> Result<(), IronError> {
|
||||
self.0.on_clipboard_paste(content.0).await.map_err(IronError)
|
||||
}
|
||||
|
||||
@@ -191,6 +215,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct SessionBuilder(<$api as RemoteDesktopApi>::SessionBuilder);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::SessionBuilder> for SessionBuilder {
|
||||
fn from(value: <$api as RemoteDesktopApi>::SessionBuilder) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SessionBuilder {
|
||||
pub fn init() -> Self {
|
||||
@@ -261,6 +291,12 @@ macro_rules! export {
|
||||
#[wasm_bindgen]
|
||||
pub struct SessionTerminationInfo(<$api as RemoteDesktopApi>::SessionTerminationInfo);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::SessionTerminationInfo> for SessionTerminationInfo {
|
||||
fn from(value: <$api as RemoteDesktopApi>::SessionTerminationInfo) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SessionTerminationInfo {
|
||||
pub fn reason(&self) -> String {
|
||||
@@ -269,50 +305,54 @@ macro_rules! export {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClipboardTransaction(<$api as RemoteDesktopApi>::ClipboardTransaction);
|
||||
pub struct ClipboardData(<$api as RemoteDesktopApi>::ClipboardData);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::ClipboardData> for ClipboardData {
|
||||
fn from(value: <$api as RemoteDesktopApi>::ClipboardData) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClipboardTransaction {
|
||||
impl ClipboardData {
|
||||
pub fn init() -> Self {
|
||||
Self(<<$api as RemoteDesktopApi>::ClipboardTransaction>::init())
|
||||
Self(<<$api as RemoteDesktopApi>::ClipboardData>::init())
|
||||
}
|
||||
|
||||
pub fn add_content(&mut self, content: ClipboardContent) {
|
||||
self.0.add_content(content.0);
|
||||
pub fn add_text(&mut self, mime_type: &str, text: &str) {
|
||||
self.0.add_text(mime_type, text);
|
||||
}
|
||||
|
||||
pub fn add_binary(&mut self, mime_type: &str, binary: &[u8]) {
|
||||
self.0.add_binary(mime_type, binary);
|
||||
}
|
||||
|
||||
pub fn items(&self) -> Vec<ClipboardItem> {
|
||||
self.0.items().into_iter().cloned().map(ClipboardItem).collect()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(&self) -> js_sys::Array {
|
||||
iron_remote_desktop::ClipboardTransaction::contents(&self.0)
|
||||
#[wasm_bindgen]
|
||||
pub struct ClipboardItem(<$api as RemoteDesktopApi>::ClipboardItem);
|
||||
|
||||
impl From<<$api as RemoteDesktopApi>::ClipboardItem> for ClipboardItem {
|
||||
fn from(value: <$api as RemoteDesktopApi>::ClipboardItem) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClipboardContent(<$api as RemoteDesktopApi>::ClipboardContent);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClipboardContent {
|
||||
pub fn new_text(mime_type: &str, text: &str) -> Self {
|
||||
Self(<<$api as RemoteDesktopApi>::ClipboardContent>::new_text(
|
||||
mime_type, text,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_binary(mime_type: &str, binary: &[u8]) -> Self {
|
||||
Self(<<$api as RemoteDesktopApi>::ClipboardContent>::new_binary(
|
||||
mime_type, binary,
|
||||
))
|
||||
}
|
||||
|
||||
impl ClipboardItem {
|
||||
pub fn mime_type(&self) -> String {
|
||||
self.0.mime_type().to_owned()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> JsValue {
|
||||
iron_remote_desktop::ClipboardContent::value(&self.0)
|
||||
self.0.value().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{js_sys, HtmlCanvasElement};
|
||||
|
||||
use crate::clipboard::ClipboardTransaction;
|
||||
use crate::clipboard::ClipboardData;
|
||||
use crate::error::IronError;
|
||||
use crate::input::InputTransaction;
|
||||
use crate::{DesktopSize, Extension};
|
||||
@@ -46,7 +46,7 @@ pub trait SessionBuilder {
|
||||
pub trait Session {
|
||||
type SessionTerminationInfo: SessionTerminationInfo;
|
||||
type InputTransaction: InputTransaction;
|
||||
type ClipboardTransaction: ClipboardTransaction;
|
||||
type ClipboardTransaction: ClipboardData;
|
||||
type Error: IronError;
|
||||
|
||||
fn run(&self) -> impl core::future::Future<Output = Result<Self::SessionTerminationInfo, Self::Error>>;
|
||||
|
||||
@@ -11,12 +11,9 @@
|
||||
//! requested: when pasting into notepad, which does not support "text/html", "text/plain"
|
||||
//! will be requested, and when pasting into WordPad, "text/html" will be requested.
|
||||
|
||||
mod transaction;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use futures_channel::mpsc;
|
||||
use iron_remote_desktop::{ClipboardContent as _, ClipboardTransaction as _};
|
||||
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackend};
|
||||
use ironrdp::cliprdr::pdu::{
|
||||
ClipboardFormat, ClipboardFormatId, ClipboardFormatName, ClipboardGeneralCapabilityFlags, FileContentsRequest,
|
||||
@@ -25,14 +22,10 @@ use ironrdp::cliprdr::pdu::{
|
||||
use ironrdp_cliprdr_format::bitmap::{dib_to_png, dibv5_to_png, png_to_cf_dibv5};
|
||||
use ironrdp_cliprdr_format::html::{cf_html_to_plain_html, plain_html_to_cf_html};
|
||||
use ironrdp_core::{impl_as_any, IntoOwned};
|
||||
use transaction::ClipboardContentValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::session::RdpInputEvent;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub(crate) use transaction::{RdpClipboardTransaction, RdpClipboardContent};
|
||||
|
||||
const MIME_TEXT: &str = "text/plain";
|
||||
const MIME_HTML: &str = "text/html";
|
||||
const MIME_PNG: &str = "image/png";
|
||||
@@ -104,7 +97,7 @@ impl WasmClipboardMessageProxy {
|
||||
/// Messages sent by the JS code or CLIPRDR to the backend implementation.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum WasmClipboardBackendMessage {
|
||||
LocalClipboardChanged(RdpClipboardTransaction),
|
||||
LocalClipboardChanged(ClipboardData),
|
||||
RemoteDataRequest(ClipboardFormatId),
|
||||
|
||||
RemoteClipboardChanged(Vec<ClipboardFormat>),
|
||||
@@ -117,8 +110,8 @@ pub(crate) enum WasmClipboardBackendMessage {
|
||||
/// Clipboard backend implementation for web. This object should be created once per session and
|
||||
/// kept alive until session is terminated.
|
||||
pub(crate) struct WasmClipboard {
|
||||
local_clipboard: Option<RdpClipboardTransaction>,
|
||||
remote_clipboard: RdpClipboardTransaction,
|
||||
local_clipboard: Option<ClipboardData>,
|
||||
remote_clipboard: ClipboardData,
|
||||
|
||||
remote_mapping: HashMap<ClipboardFormatId, String>,
|
||||
remote_formats_to_read: Vec<ClipboardFormatId>,
|
||||
@@ -138,7 +131,7 @@ impl WasmClipboard {
|
||||
pub(crate) fn new(message_proxy: WasmClipboardMessageProxy, js_callbacks: JsClipboardCallbacks) -> Self {
|
||||
Self {
|
||||
local_clipboard: None,
|
||||
remote_clipboard: RdpClipboardTransaction::init(),
|
||||
remote_clipboard: ClipboardData::new(),
|
||||
proxy: message_proxy,
|
||||
js_callbacks,
|
||||
|
||||
@@ -154,13 +147,10 @@ impl WasmClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_local_clipboard_changed(
|
||||
&mut self,
|
||||
transaction: RdpClipboardTransaction,
|
||||
) -> anyhow::Result<Vec<ClipboardFormat>> {
|
||||
fn handle_local_clipboard_changed(&mut self, transaction: ClipboardData) -> anyhow::Result<Vec<ClipboardFormat>> {
|
||||
let mut formats = Vec::new();
|
||||
transaction.contents().iter().for_each(|content| {
|
||||
match content.mime_type() {
|
||||
transaction.items().iter().for_each(|content| {
|
||||
match content.mime_type.as_str() {
|
||||
MIME_TEXT => formats.push(ClipboardFormat::new(ClipboardFormatId::CF_UNICODETEXT)),
|
||||
MIME_HTML => {
|
||||
formats.extend([
|
||||
@@ -204,15 +194,15 @@ impl WasmClipboard {
|
||||
|
||||
let find_content_by_mime = |mime: &str| {
|
||||
transaction
|
||||
.contents()
|
||||
.items()
|
||||
.iter()
|
||||
.find(|content| content.mime_type() == mime)
|
||||
.find(|content| content.mime_type.as_str() == mime)
|
||||
};
|
||||
|
||||
let find_text_content_by_mime = |mime: &str| {
|
||||
find_content_by_mime(mime)
|
||||
.and_then(|content| {
|
||||
if let ClipboardContentValue::Text(text) = content.value() {
|
||||
if let ClipboardItemValue::Text(text) = &content.value {
|
||||
Some(text.as_str())
|
||||
} else {
|
||||
None
|
||||
@@ -224,7 +214,7 @@ impl WasmClipboard {
|
||||
let find_binary_content_by_mime = |mime: &str| {
|
||||
find_content_by_mime(mime)
|
||||
.and_then(|content| {
|
||||
if let ClipboardContentValue::Binary(binary) = content.value() {
|
||||
if let ClipboardItemValue::Binary(binary) = &content.value {
|
||||
Some(binary.as_slice())
|
||||
} else {
|
||||
None
|
||||
@@ -372,21 +362,21 @@ impl WasmClipboard {
|
||||
|
||||
let content = match pending_format {
|
||||
ClipboardFormatId::CF_UNICODETEXT => match response.to_unicode_string() {
|
||||
Ok(text) => Some(RdpClipboardContent::new_text(MIME_TEXT, &text)),
|
||||
Ok(text) => Some(ClipboardItem::new_text(MIME_TEXT, text)),
|
||||
Err(err) => {
|
||||
error!("CF_UNICODETEXT decode error: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
ClipboardFormatId::CF_DIB => match dib_to_png(response.data()) {
|
||||
Ok(png) => Some(RdpClipboardContent::new_binary(MIME_PNG, &png)),
|
||||
Ok(png) => Some(ClipboardItem::new_binary(MIME_PNG, png)),
|
||||
Err(err) => {
|
||||
warn!("DIB decode error: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
ClipboardFormatId::CF_DIBV5 => match dibv5_to_png(response.data()) {
|
||||
Ok(png) => Some(RdpClipboardContent::new_binary(MIME_PNG, &png)),
|
||||
Ok(png) => Some(ClipboardItem::new_binary(MIME_PNG, png)),
|
||||
Err(err) => {
|
||||
warn!("DIBv5 decode error: {}", err);
|
||||
None
|
||||
@@ -394,23 +384,24 @@ impl WasmClipboard {
|
||||
},
|
||||
registered => {
|
||||
let format_name = self.remote_mapping.get(®istered).map(|s| s.as_str());
|
||||
|
||||
match format_name {
|
||||
Some(FORMAT_WIN_HTML_NAME) => match cf_html_to_plain_html(response.data()) {
|
||||
Ok(text) => Some(RdpClipboardContent::new_text(MIME_HTML, text)),
|
||||
Ok(text) => Some(ClipboardItem::new_text(MIME_HTML, text.to_owned())),
|
||||
Err(err) => {
|
||||
warn!("CF_HTML decode error: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(FORMAT_MIME_HTML_NAME) => match response.to_string() {
|
||||
Ok(text) => Some(RdpClipboardContent::new_text(MIME_HTML, &text)),
|
||||
Ok(text) => Some(ClipboardItem::new_text(MIME_HTML, text)),
|
||||
Err(err) => {
|
||||
warn!("text/html decode error: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(FORMAT_MIME_PNG_NAME) | Some(FORMAT_PNG_NAME) => {
|
||||
Some(RdpClipboardContent::new_binary(MIME_PNG, response.data()))
|
||||
Some(ClipboardItem::new_binary(MIME_PNG, response.data().to_owned()))
|
||||
}
|
||||
_ => {
|
||||
// Not supported format
|
||||
@@ -421,7 +412,7 @@ impl WasmClipboard {
|
||||
};
|
||||
|
||||
if let Some(content) = content {
|
||||
self.remote_clipboard.add_content(content);
|
||||
self.remote_clipboard.add(content);
|
||||
}
|
||||
|
||||
if let Some(format) = self.remote_formats_to_read.last() {
|
||||
@@ -429,16 +420,20 @@ impl WasmClipboard {
|
||||
self.proxy
|
||||
.send_cliprdr_message(ClipboardMessage::SendInitiatePaste(*format));
|
||||
} else {
|
||||
// All formats were read, send clipboard to JS
|
||||
let transaction = core::mem::take(&mut self.remote_clipboard);
|
||||
if transaction.is_empty() {
|
||||
// All formats were read, send clipboard to JS.
|
||||
let clipboard_data = core::mem::take(&mut self.remote_clipboard);
|
||||
|
||||
if clipboard_data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Set clipboard when all formats were read
|
||||
// Set clipboard when all formats were read.
|
||||
self.js_callbacks
|
||||
.on_remote_clipboard_changed
|
||||
.call1(&JsValue::NULL, &JsValue::from(transaction))
|
||||
.call1(
|
||||
&JsValue::NULL,
|
||||
&JsValue::from(crate::__wasm_ffi::ClipboardData::from(clipboard_data)),
|
||||
)
|
||||
.expect("failed to call JS callback");
|
||||
}
|
||||
|
||||
@@ -454,18 +449,18 @@ impl WasmClipboard {
|
||||
self.proxy
|
||||
.send_cliprdr_message(ClipboardMessage::SendInitiateCopy(formats));
|
||||
}
|
||||
Err(err) => {
|
||||
// Not a critical error, we could skip single clipboard update
|
||||
error!("Failed to handle local clipboard change: {}", err);
|
||||
Err(e) => {
|
||||
// Not a critical error, we could skip single clipboard update.
|
||||
error!(error = format!("{e:#}"), "Failed to handle local clipboard change");
|
||||
}
|
||||
}
|
||||
}
|
||||
WasmClipboardBackendMessage::RemoteDataRequest(format) => {
|
||||
let message = match self.process_remote_data_request(format) {
|
||||
Ok(message) => message,
|
||||
Err(err) => {
|
||||
// Not a critical error, but we should notify remote about error
|
||||
error!("Failed to process remote data request: {}", err);
|
||||
Err(e) => {
|
||||
// Not a critical error, but we should notify remote about it.
|
||||
error!(error = format!("{e:#}"), "Failed to process remote data request");
|
||||
FormatDataResponse::new_error()
|
||||
}
|
||||
};
|
||||
@@ -483,32 +478,31 @@ impl WasmClipboard {
|
||||
Ok(None) => {
|
||||
// No formats to query
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to process remote clipboard change: {}", err);
|
||||
Err(e) => {
|
||||
error!(error = format!("{e:#}"), "Failed to process remote clipboard change");
|
||||
}
|
||||
}
|
||||
}
|
||||
WasmClipboardBackendMessage::RemoteDataResponse(formats) => {
|
||||
match self.process_remote_data_response(formats) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!("Failed to process remote data response: {}", err);
|
||||
Err(e) => {
|
||||
error!(error = format!("{e:#}"), "Failed to process remote data response");
|
||||
}
|
||||
}
|
||||
}
|
||||
WasmClipboardBackendMessage::FormatListReceived => {
|
||||
if let Some(callback) = self.js_callbacks.on_remote_received_format_list.as_mut() {
|
||||
callback.call0(&JsValue::NULL).expect("Failed to call JS callback");
|
||||
callback.call0(&JsValue::NULL).expect("failed to call JS callback");
|
||||
}
|
||||
}
|
||||
WasmClipboardBackendMessage::ForceClipboardUpdate => {
|
||||
if let Some(callback) = self.js_callbacks.on_force_clipboard_update.as_mut() {
|
||||
callback.call0(&JsValue::NULL).expect("Failed to call JS callback");
|
||||
callback.call0(&JsValue::NULL).expect("failed to call JS callback");
|
||||
} else {
|
||||
// If no initial clipboard callback was set, send empty format list instead
|
||||
return self.process_event(WasmClipboardBackendMessage::LocalClipboardChanged(
|
||||
RdpClipboardTransaction::init(),
|
||||
));
|
||||
return self
|
||||
.process_event(WasmClipboardBackendMessage::LocalClipboardChanged(ClipboardData::new()));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -587,3 +581,108 @@ impl CliprdrBackend for WasmClipboardBackend {
|
||||
// File transfer not implemented yet
|
||||
}
|
||||
}
|
||||
|
||||
/// Object which represents complete clipboard transaction with multiple MIME types.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct ClipboardData {
|
||||
items: Vec<ClipboardItem>,
|
||||
}
|
||||
|
||||
impl ClipboardData {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { items: Vec::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, item: ClipboardItem) {
|
||||
self.items.push(item);
|
||||
}
|
||||
|
||||
pub(crate) fn items(&self) -> &[ClipboardItem] {
|
||||
&self.items
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.items.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl iron_remote_desktop::ClipboardData for ClipboardData {
|
||||
type Item = ClipboardItem;
|
||||
|
||||
fn init() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn add_text(&mut self, mime_type: &str, text: &str) {
|
||||
self.items.push(ClipboardItem {
|
||||
mime_type: mime_type.to_owned(),
|
||||
value: ClipboardItemValue::Text(text.to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_binary(&mut self, mime_type: &str, binary: &[u8]) {
|
||||
self.items.push(ClipboardItem {
|
||||
mime_type: mime_type.to_owned(),
|
||||
value: ClipboardItemValue::Binary(binary.to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
fn items(&self) -> &[Self::Item] {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<ClipboardItem> for ClipboardData {
|
||||
fn from_iter<T: IntoIterator<Item = ClipboardItem>>(iter: T) -> Self {
|
||||
Self {
|
||||
items: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ClipboardItemValue {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Object which represents single clipboard format represented standard MIME type.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ClipboardItem {
|
||||
pub(crate) mime_type: String,
|
||||
pub(crate) value: ClipboardItemValue,
|
||||
}
|
||||
|
||||
impl ClipboardItem {
|
||||
pub(crate) fn new_text(mime_type: impl Into<String>, text: String) -> Self {
|
||||
Self {
|
||||
mime_type: mime_type.into(),
|
||||
value: ClipboardItemValue::Text(text),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_binary(mime_type: impl Into<String>, payload: Vec<u8>) -> Self {
|
||||
Self {
|
||||
mime_type: mime_type.into(),
|
||||
value: ClipboardItemValue::Binary(payload),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl iron_remote_desktop::ClipboardItem for ClipboardItem {
|
||||
fn mime_type(&self) -> &str {
|
||||
&self.mime_type
|
||||
}
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn value(&self) -> JsValue {
|
||||
match &self.value {
|
||||
ClipboardItemValue::Text(text) => JsValue::from_str(text),
|
||||
ClipboardItemValue::Binary(binary) => JsValue::from(js_sys::Uint8Array::from(binary.as_slice())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Object which represents complete clipboard transaction with multiple MIME types.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct RdpClipboardTransaction {
|
||||
contents: Vec<RdpClipboardContent>,
|
||||
}
|
||||
|
||||
impl RdpClipboardTransaction {
|
||||
pub(crate) fn contents(&self) -> &[RdpClipboardContent] {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.contents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl iron_remote_desktop::ClipboardTransaction for RdpClipboardTransaction {
|
||||
type ClipboardContent = RdpClipboardContent;
|
||||
|
||||
fn init() -> Self {
|
||||
Self { contents: Vec::new() }
|
||||
}
|
||||
|
||||
fn add_content(&mut self, content: Self::ClipboardContent) {
|
||||
self.contents.push(content);
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.contents.is_empty()
|
||||
}
|
||||
|
||||
fn contents(&self) -> js_sys::Array {
|
||||
js_sys::Array::from_iter(
|
||||
self.contents
|
||||
.iter()
|
||||
.map(|content: &RdpClipboardContent| JsValue::from(content.clone())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<RdpClipboardContent> for RdpClipboardTransaction {
|
||||
fn from_iter<T: IntoIterator<Item = RdpClipboardContent>>(iter: T) -> Self {
|
||||
Self {
|
||||
contents: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ClipboardContentValue {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
}
|
||||
|
||||
impl ClipboardContentValue {
|
||||
pub(crate) fn value(&self) -> JsValue {
|
||||
match self {
|
||||
ClipboardContentValue::Text(text) => JsValue::from_str(text),
|
||||
ClipboardContentValue::Binary(binary) => js_sys::Uint8Array::from(binary.as_slice()).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Object which represents single clipboard format represented standard MIME type.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RdpClipboardContent {
|
||||
mime_type: String,
|
||||
value: ClipboardContentValue,
|
||||
}
|
||||
|
||||
impl RdpClipboardContent {
|
||||
pub(crate) fn mime_type(&self) -> &str {
|
||||
&self.mime_type
|
||||
}
|
||||
|
||||
pub(crate) fn value(&self) -> &ClipboardContentValue {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl iron_remote_desktop::ClipboardContent for RdpClipboardContent {
|
||||
fn new_text(mime_type: &str, text: &str) -> Self {
|
||||
Self {
|
||||
mime_type: mime_type.into(),
|
||||
value: ClipboardContentValue::Text(text.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_binary(mime_type: &str, binary: &[u8]) -> Self {
|
||||
Self {
|
||||
mime_type: mime_type.into(),
|
||||
value: ClipboardContentValue::Binary(binary.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
fn mime_type(&self) -> &str {
|
||||
self.mime_type.as_str()
|
||||
}
|
||||
|
||||
fn value(&self) -> JsValue {
|
||||
self.value.value()
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@ impl RemoteDesktopApi for Api {
|
||||
type SessionTerminationInfo = session::SessionTerminationInfo;
|
||||
type DeviceEvent = input::DeviceEvent;
|
||||
type InputTransaction = input::InputTransaction;
|
||||
type ClipboardTransaction = clipboard::RdpClipboardTransaction;
|
||||
type ClipboardContent = clipboard::RdpClipboardContent;
|
||||
type ClipboardData = clipboard::ClipboardData;
|
||||
type ClipboardItem = clipboard::ClipboardItem;
|
||||
type Error = error::IronError;
|
||||
|
||||
fn post_setup() {
|
||||
|
||||
@@ -35,7 +35,7 @@ use web_sys::HtmlCanvasElement;
|
||||
|
||||
use crate::canvas::Canvas;
|
||||
use crate::clipboard;
|
||||
use crate::clipboard::{RdpClipboardTransaction, WasmClipboard, WasmClipboardBackend, WasmClipboardBackendMessage};
|
||||
use crate::clipboard::{ClipboardData, WasmClipboard, WasmClipboardBackend, WasmClipboardBackendMessage};
|
||||
use crate::error::IronError;
|
||||
use crate::image::extract_partial_image;
|
||||
use crate::input::InputTransaction;
|
||||
@@ -433,7 +433,7 @@ impl Session {
|
||||
impl iron_remote_desktop::Session for Session {
|
||||
type SessionTerminationInfo = SessionTerminationInfo;
|
||||
type InputTransaction = InputTransaction;
|
||||
type ClipboardTransaction = RdpClipboardTransaction;
|
||||
type ClipboardTransaction = ClipboardData;
|
||||
type Error = IronError;
|
||||
|
||||
async fn run(&self) -> Result<Self::SessionTerminationInfo, Self::Error> {
|
||||
|
||||
@@ -7,8 +7,8 @@ import init, {
|
||||
Session,
|
||||
SessionBuilder,
|
||||
SessionTerminationInfo,
|
||||
ClipboardTransaction,
|
||||
ClipboardContent,
|
||||
ClipboardData,
|
||||
ClipboardItem,
|
||||
Extension,
|
||||
} from '../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||
|
||||
@@ -20,8 +20,8 @@ export default {
|
||||
InputTransaction,
|
||||
IronError,
|
||||
SessionBuilder,
|
||||
ClipboardTransaction,
|
||||
ClipboardContent,
|
||||
ClipboardData,
|
||||
ClipboardItem,
|
||||
Session,
|
||||
SessionTerminationInfo,
|
||||
Extension,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface ClipboardContent {
|
||||
new_text(mime_type: string, text: string): ClipboardContent;
|
||||
new_binary(mime_type: string, binary: Uint8Array): ClipboardContent;
|
||||
mime_type(): string;
|
||||
value(): string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { ClipboardItem } from './ClipboardItem';
|
||||
|
||||
export interface ClipboardData {
|
||||
init(): ClipboardData;
|
||||
add_text(mime_type: string, text: string): void;
|
||||
add_binary(mime_type: string, binary: Uint8Array): void;
|
||||
items(): ClipboardItem[];
|
||||
is_empty(): boolean;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ClipboardItem {
|
||||
mime_type(): string;
|
||||
value(): string | Uint8Array;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { ClipboardContent } from './ClipboardContent';
|
||||
|
||||
export interface ClipboardTransaction {
|
||||
init(): ClipboardTransaction;
|
||||
add_content(content: ClipboardContent): void;
|
||||
is_empty(): boolean;
|
||||
content(): Array<ClipboardContent>;
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import type { IronError } from './session-event';
|
||||
import type { Session } from './Session';
|
||||
import type { SessionBuilder } from './SessionBuilder';
|
||||
import type { SessionTerminationInfo } from './SessionTerminationInfo';
|
||||
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||
import type { ClipboardContent } from './ClipboardContent';
|
||||
import type { ClipboardData } from './ClipboardData';
|
||||
import type { ClipboardItem } from './ClipboardItem';
|
||||
|
||||
export interface RemoteDesktopModule {
|
||||
init: () => Promise<unknown>;
|
||||
@@ -18,6 +18,6 @@ export interface RemoteDesktopModule {
|
||||
Session: Session;
|
||||
SessionBuilder: SessionBuilder;
|
||||
SessionTerminationInfo: SessionTerminationInfo;
|
||||
ClipboardTransaction: ClipboardTransaction;
|
||||
ClipboardContent: ClipboardContent;
|
||||
ClipboardData: ClipboardData;
|
||||
ClipboardItem: ClipboardItem;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { InputTransaction } from './InputTransaction';
|
||||
import type { DesktopSize } from './DesktopSize';
|
||||
import type { SessionTerminationInfo } from './SessionTerminationInfo';
|
||||
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||
import type { ClipboardData } from './ClipboardData';
|
||||
|
||||
export interface Session {
|
||||
run(): Promise<SessionTerminationInfo>;
|
||||
@@ -11,7 +11,7 @@ export interface Session {
|
||||
synchronize_lock_keys(scroll_lock: boolean, num_lock: boolean, caps_lock: boolean, kana_lock: boolean): void;
|
||||
extension_call(value: unknown): unknown;
|
||||
shutdown(): void;
|
||||
on_clipboard_paste(content: ClipboardTransaction): Promise<void>;
|
||||
on_clipboard_paste(data: ClipboardData): Promise<void>;
|
||||
resize(
|
||||
width: number,
|
||||
height: number,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Session } from './Session';
|
||||
import type { DesktopSize } from './DesktopSize';
|
||||
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||
import type { ClipboardData } from './ClipboardData';
|
||||
|
||||
export interface SessionBuilder {
|
||||
init(): SessionBuilder;
|
||||
@@ -76,7 +76,7 @@ interface SetCursorStyleCallback {
|
||||
}
|
||||
|
||||
interface RemoteClipboardChangedCallback {
|
||||
(transaction: ClipboardTransaction): void;
|
||||
(data: ClipboardData): void;
|
||||
}
|
||||
|
||||
interface RemoteReceiveForwardListCallback {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||
import { PublicAPI } from './services/PublicAPI';
|
||||
import { ScreenScale } from './enums/ScreenScale';
|
||||
import type { ClipboardTransaction } from './interfaces/ClipboardTransaction';
|
||||
import type { ClipboardData } from './interfaces/ClipboardData';
|
||||
import type { RemoteDesktopModule } from './interfaces/RemoteDesktopModule';
|
||||
|
||||
let {
|
||||
@@ -68,14 +68,14 @@
|
||||
|
||||
let isClipboardApiSupported = false;
|
||||
let lastClientClipboardItems = new Map<string, string | Uint8Array>();
|
||||
let lastClientClipboardTransaction: ClipboardTransaction | null = null;
|
||||
let lastClientClipboardData: ClipboardData | null = null;
|
||||
let lastClipboardMonitorLoopError: Error | null = null;
|
||||
|
||||
/* Firefox-specific BEGIN */
|
||||
|
||||
// See `ffRemoteClipboardTransaction` variable docs below
|
||||
const FF_REMOTE_CLIPBOARD_TRANSACTION_SET_RETRY_INTERVAL = 100; // ms
|
||||
const FF_REMOTE_CLIPBOARD_TRANSACTION_SET_MAX_RETRIES = 30; // 3 seconds (100ms * 30)
|
||||
// See `ffRemoteClipboardData` variable docs below
|
||||
const FF_REMOTE_CLIPBOARD_DATA_SET_RETRY_INTERVAL = 100; // ms
|
||||
const FF_REMOTE_CLIPBOARD_DATA_SET_MAX_RETRIES = 30; // 3 seconds (100ms * 30)
|
||||
// On Firefox, this interval is used to stop delaying the keyboard events if the paste event has
|
||||
// failed and we haven't received any clipboard data from the remote side.
|
||||
const FF_LOCAL_CLIPBOARD_COPY_TIMEOUT = 1000; // 1s (For text-only data this should be enough)
|
||||
@@ -84,10 +84,10 @@
|
||||
// called in scope of user-initiated event processing (e.g. keyboard event), but we receive
|
||||
// clipboard data from the remote side asynchronously in wasm service callback. therefore we
|
||||
// set this variable in callback and use its value on the user-initiated copy event.
|
||||
let ffRemoteClipboardTransaction: ClipboardTransaction | null = null;
|
||||
let ffRemoteClipboardData: ClipboardData | null = null;
|
||||
// For Firefox we need this variable to perform wait loop for the remote side to finish sending
|
||||
// clipboard content to the client.
|
||||
let ffRemoteClipboardTransactionRetriesLeft = 0;
|
||||
let ffRemoteClipboardDataRetriesLeft = 0;
|
||||
let ffPostponeKeyboardEvents = false;
|
||||
let ffDelayedKeyboardEvents: KeyboardEvent[] = [];
|
||||
let ffCnavasFocused = false;
|
||||
@@ -131,12 +131,12 @@
|
||||
return (evt.ctrlKey && evt.code === 'KeyV') || evt.code == 'Paste';
|
||||
}
|
||||
|
||||
// This function is required to covert `ClipboardTransaction` to a object that can be used
|
||||
// This function is required to convert `ClipboardData` to a object that can be used
|
||||
// with `ClipboardItem` API.
|
||||
function clipboardTransactionToRecord(transaction: ClipboardTransaction): Record<string, Blob> {
|
||||
function clipboardDataToRecord(data: ClipboardData): Record<string, Blob> {
|
||||
let result = {} as Record<string, Blob>;
|
||||
|
||||
for (const item of transaction.content()) {
|
||||
for (const item of data.items()) {
|
||||
let mime = item.mime_type();
|
||||
let value = new Blob([item.value()], { type: mime });
|
||||
|
||||
@@ -149,8 +149,8 @@
|
||||
// This callback is required to send initial clipboard state if available.
|
||||
function onForceClipboardUpdate() {
|
||||
try {
|
||||
if (lastClientClipboardTransaction) {
|
||||
remoteDesktopService.onClipboardChanged(lastClientClipboardTransaction);
|
||||
if (lastClientClipboardData) {
|
||||
remoteDesktopService.onClipboardChanged(lastClientClipboardData);
|
||||
} else {
|
||||
remoteDesktopService.onClipboardChangedEmpty();
|
||||
}
|
||||
@@ -160,9 +160,9 @@
|
||||
}
|
||||
|
||||
// This callback is required to update client clipboard state when remote side has changed.
|
||||
function onRemoteClipboardChanged(transaction: ClipboardTransaction) {
|
||||
function onRemoteClipboardChanged(data: ClipboardData) {
|
||||
try {
|
||||
const mime_formats = clipboardTransactionToRecord(transaction);
|
||||
const mime_formats = clipboardDataToRecord(data);
|
||||
const clipboard_item = new ClipboardItem(mime_formats);
|
||||
navigator.clipboard.write([clipboard_item]);
|
||||
} catch (err) {
|
||||
@@ -236,7 +236,7 @@
|
||||
if (!sameValue) {
|
||||
lastClientClipboardItems = values;
|
||||
|
||||
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||
let data = remoteDesktopService.createClipboardData();
|
||||
|
||||
// Iterate over `Record` type
|
||||
values.forEach((value: string | Uint8Array, key: string) => {
|
||||
@@ -246,15 +246,15 @@
|
||||
}
|
||||
|
||||
if (key.startsWith('text/') && typeof value === 'string') {
|
||||
transaction.add_content(remoteDesktopService.constructClipboardContentFromText(key, value));
|
||||
data.add_text(key, value);
|
||||
} else if (key.startsWith('image/') && value instanceof Uint8Array) {
|
||||
transaction.add_content(remoteDesktopService.constructClipboardContentFromBinary(key, value));
|
||||
data.add_binary(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
lastClientClipboardTransaction = transaction;
|
||||
remoteDesktopService.onClipboardChanged(transaction);
|
||||
if (!data.is_empty()) {
|
||||
lastClientClipboardData = data;
|
||||
remoteDesktopService.onClipboardChanged(data);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -286,28 +286,35 @@
|
||||
|
||||
// Only set variable on callback, the real clipboard update will be performed in keyboard
|
||||
// callback. (User-initiated event is required for Firefox to allow clipboard write)
|
||||
function ffOnRemoteClipboardChanged(transaction: ClipboardTransaction) {
|
||||
ffRemoteClipboardTransaction = transaction;
|
||||
function ffOnRemoteClipboardChanged(data: ClipboardData) {
|
||||
ffRemoteClipboardData = data;
|
||||
}
|
||||
|
||||
function ffWaitForRemoteClipboardTransactionSet() {
|
||||
if (ffRemoteClipboardTransaction) {
|
||||
function ffWaitForRemoteClipboardDataSet() {
|
||||
if (ffRemoteClipboardData) {
|
||||
try {
|
||||
let transaction = ffRemoteClipboardTransaction;
|
||||
ffRemoteClipboardTransaction = null;
|
||||
for (const content of transaction.content()) {
|
||||
let clipboard_data = ffRemoteClipboardData;
|
||||
ffRemoteClipboardData = null;
|
||||
for (const item of clipboard_data.items()) {
|
||||
// Firefox only supports text/plain mime type for clipboard writes :(
|
||||
if (content.mime_type() === 'text/plain') {
|
||||
navigator.clipboard.writeText(content.value());
|
||||
if (item.mime_type() === 'text/plain') {
|
||||
const value = item.value();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
navigator.clipboard.writeText(value);
|
||||
} else {
|
||||
loggingService.error('Unexpected value for text/plain clipboard item');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to set client clipboard: ' + err);
|
||||
}
|
||||
} else if (ffRemoteClipboardTransactionRetriesLeft > 0) {
|
||||
ffRemoteClipboardTransactionRetriesLeft--;
|
||||
setTimeout(ffWaitForRemoteClipboardTransactionSet, FF_REMOTE_CLIPBOARD_TRANSACTION_SET_RETRY_INTERVAL);
|
||||
} else if (ffRemoteClipboardDataRetriesLeft > 0) {
|
||||
ffRemoteClipboardDataRetriesLeft--;
|
||||
setTimeout(ffWaitForRemoteClipboardDataSet, FF_REMOTE_CLIPBOARD_DATA_SET_RETRY_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +341,7 @@
|
||||
}
|
||||
|
||||
try {
|
||||
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||
let clipboard_data = remoteDesktopService.createClipboardData();
|
||||
|
||||
if (evt.clipboardData == null) {
|
||||
return;
|
||||
@@ -345,11 +352,10 @@
|
||||
|
||||
if (mime.startsWith('text/')) {
|
||||
clipItem.getAsString((str: string) => {
|
||||
let content = remoteDesktopService.constructClipboardContentFromText(mime, str);
|
||||
transaction.add_content(content);
|
||||
clipboard_data.add_text(mime, str);
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
remoteDesktopService.onClipboardChanged(transaction as ClipboardTransaction);
|
||||
if (!clipboard_data.is_empty()) {
|
||||
remoteDesktopService.onClipboardChanged(clipboard_data);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@@ -363,11 +369,11 @@
|
||||
|
||||
file.arrayBuffer().then((buffer: ArrayBuffer) => {
|
||||
const strict_buffer = new Uint8Array(buffer);
|
||||
let content = remoteDesktopService.constructClipboardContentFromBinary(mime, strict_buffer);
|
||||
transaction.add_content(content);
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
remoteDesktopService.onClipboardChanged(transaction);
|
||||
clipboard_data.add_binary(mime, strict_buffer);
|
||||
|
||||
if (!clipboard_data.is_empty()) {
|
||||
remoteDesktopService.onClipboardChanged(clipboard_data);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@@ -634,8 +640,8 @@
|
||||
// only after some user-initiated event (e.g. keyboard event).
|
||||
// therefore we need to wait here for the clipboard data to be ready.
|
||||
|
||||
ffRemoteClipboardTransactionRetriesLeft = FF_REMOTE_CLIPBOARD_TRANSACTION_SET_MAX_RETRIES;
|
||||
ffWaitForRemoteClipboardTransactionSet();
|
||||
ffRemoteClipboardDataRetriesLeft = FF_REMOTE_CLIPBOARD_DATA_SET_MAX_RETRIES;
|
||||
ffWaitForRemoteClipboardDataSet();
|
||||
}
|
||||
|
||||
remoteDesktopService.sendKeyboardEvent(evt);
|
||||
|
||||
@@ -6,8 +6,8 @@ export type { DesktopSize } from './interfaces/DesktopSize';
|
||||
export type { SessionEvent, IronError, IronErrorKind } from './interfaces/session-event';
|
||||
export type { SessionEventType } from './enums/SessionEventType';
|
||||
export type { SessionTerminationInfo } from './interfaces/SessionTerminationInfo';
|
||||
export type { ClipboardTransaction } from './interfaces/ClipboardTransaction';
|
||||
export type { ClipboardContent } from './interfaces/ClipboardContent';
|
||||
export type { ClipboardData } from './interfaces/ClipboardData';
|
||||
export type { ClipboardItem } from './interfaces/ClipboardItem';
|
||||
export type { DeviceEvent } from './interfaces/DeviceEvent';
|
||||
export type { InputTransaction } from './interfaces/InputTransaction';
|
||||
export type { Session } from './interfaces/Session';
|
||||
|
||||
@@ -13,8 +13,7 @@ import type { ResizeEvent } from '../interfaces/ResizeEvent';
|
||||
import { ScreenScale } from '../enums/ScreenScale';
|
||||
import type { MousePosition } from '../interfaces/MousePosition';
|
||||
import type { SessionEvent, IronErrorKind, IronError } from '../interfaces/session-event';
|
||||
import type { ClipboardTransaction } from '../interfaces/ClipboardTransaction';
|
||||
import type { ClipboardContent } from '../interfaces/ClipboardContent';
|
||||
import type { ClipboardData } from '../interfaces/ClipboardData';
|
||||
import type { Session } from '../interfaces/Session';
|
||||
import type { DeviceEvent } from '../interfaces/DeviceEvent';
|
||||
import type { SessionTerminationInfo } from '../interfaces/SessionTerminationInfo';
|
||||
@@ -22,7 +21,7 @@ import type { RemoteDesktopModule } from '../interfaces/RemoteDesktopModule';
|
||||
import { ConfigBuilder } from './ConfigBuilder';
|
||||
import type { Config } from './Config';
|
||||
|
||||
type OnRemoteClipboardChanged = (transaction: ClipboardTransaction) => void;
|
||||
type OnRemoteClipboardChanged = (data: ClipboardData) => void;
|
||||
type OnRemoteReceivedFormatsList = () => void;
|
||||
type OnForceClipboardUpdate = () => void;
|
||||
|
||||
@@ -65,16 +64,8 @@ export class RemoteDesktopService {
|
||||
loggingService.info('Web bridge initialized.');
|
||||
}
|
||||
|
||||
constructClipboardTransaction(): ClipboardTransaction {
|
||||
return this.module.ClipboardTransaction.init();
|
||||
}
|
||||
|
||||
constructClipboardContentFromText(mime_type: string, text: string): ClipboardContent {
|
||||
return this.module.ClipboardContent.new_text(mime_type, text);
|
||||
}
|
||||
|
||||
constructClipboardContentFromBinary(mime_type: string, binary: Uint8Array): ClipboardContent {
|
||||
return this.module.ClipboardContent.new_binary(mime_type, binary);
|
||||
createClipboardData(): ClipboardData {
|
||||
return this.module.ClipboardData.init();
|
||||
}
|
||||
|
||||
async init(debug: LogType) {
|
||||
@@ -273,16 +264,16 @@ export class RemoteDesktopService {
|
||||
|
||||
/// Triggered by the browser when local clipboard is updated. Clipboard backend should
|
||||
/// cache the content and send it to the server when it is requested.
|
||||
onClipboardChanged(transaction: ClipboardTransaction): Promise<void> {
|
||||
onClipboardChanged(data: ClipboardData): Promise<void> {
|
||||
const onClipboardChangedPromise = async () => {
|
||||
await this.session?.on_clipboard_paste(transaction);
|
||||
await this.session?.on_clipboard_paste(data);
|
||||
};
|
||||
return onClipboardChangedPromise();
|
||||
}
|
||||
|
||||
onClipboardChangedEmpty(): Promise<void> {
|
||||
const onClipboardChangedPromise = async () => {
|
||||
await this.session?.on_clipboard_paste(this.module.ClipboardTransaction.init());
|
||||
await this.session?.on_clipboard_paste(this.module.ClipboardData.init());
|
||||
};
|
||||
return onClipboardChangedPromise();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user