Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions crates/ironrdp-web/src/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ pub(crate) enum WasmClipboardBackendMessage {
LocksExpired {
clip_data_ids: Vec<u32>,
},
/// [MS-RDPECLIP] 2.2.3.2 Remote's response to one of our outbound Format Lists.
///
/// `ok` is `true` when the remote accepted the advertised formats
/// (`CB_RESPONSE_OK`) and `false` when it rejected them (`CB_RESPONSE_FAIL`).
/// A rejected advertise is silently discarded by the remote, so a file paste
/// cannot proceed; backends use this to detect and recover from a refused
/// paste instead of stalling until a lock timeout.
FormatListResponse {
ok: bool,
},

// JS-initiated file transfer operations
/// JS requests file contents from remote (download).
Expand Down Expand Up @@ -235,6 +245,7 @@ pub(crate) struct JsClipboardCallbacks {
pub(crate) on_lock: Option<js_sys::Function>,
pub(crate) on_unlock: Option<js_sys::Function>,
pub(crate) on_locks_expired: Option<js_sys::Function>,
pub(crate) on_format_list_response: Option<js_sys::Function>,
}

impl WasmClipboard {
Expand Down Expand Up @@ -841,6 +852,16 @@ impl WasmClipboard {
);
}
}
WasmClipboardBackendMessage::FormatListResponse { ok } => {
if let Some(callback) = self.js_callbacks.on_format_list_response.as_ref() {
if let Err(e) = callback.call1(&JsValue::NULL, &JsValue::from_bool(ok)) {
error!(error = ?e, ok, "Failed to call JS format list response callback");
return Ok(());
}
} else {
trace!(ok, "Format list response received but no JS callback registered");
}
}
// The following variants are handled directly in the event loop and should never reach here
WasmClipboardBackendMessage::FileContentsRequestSend { .. }
| WasmClipboardBackendMessage::FileContentsResponseSend { .. }
Expand Down Expand Up @@ -948,6 +969,10 @@ impl CliprdrBackend for WasmClipboardBackend {
self.send_event(WasmClipboardBackendMessage::Unlock { data_id });
}

fn on_format_list_response(&mut self, ok: bool) {
self.send_event(WasmClipboardBackendMessage::FormatListResponse { ok });
}

fn on_remote_file_list(&mut self, files: &[ironrdp::cliprdr::pdu::FileDescriptor], clip_data_id: Option<u32>) {
let file_metadata: Vec<FileMetadata> = files.iter().map(FileMetadata::from_file_descriptor).collect();

Expand Down Expand Up @@ -1227,6 +1252,7 @@ mod tests {
on_lock: Some(js_sys::Function::new_no_args("")),
on_unlock: Some(js_sys::Function::new_no_args("")),
on_locks_expired: Some(js_sys::Function::new_no_args("")),
on_format_list_response: Some(js_sys::Function::new_no_args("")),
}
}

Expand Down
8 changes: 8 additions & 0 deletions crates/ironrdp-web/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct SessionBuilderInner {
lock_callback: Option<js_sys::Function>,
unlock_callback: Option<js_sys::Function>,
locks_expired_callback: Option<js_sys::Function>,
format_list_response_callback: Option<js_sys::Function>,

// Setting printer stream callbacks activates the virtual printer.
invalid_print_job_stream_callbacks: bool,
Expand Down Expand Up @@ -120,6 +121,7 @@ impl Default for SessionBuilderInner {
lock_callback: None,
unlock_callback: None,
locks_expired_callback: None,
format_list_response_callback: None,

invalid_print_job_stream_callbacks: false,
print_job_stream_callbacks: None,
Expand Down Expand Up @@ -276,6 +278,9 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
|locks_expired_callback: JsValue| {
self.0.borrow_mut().locks_expired_callback = locks_expired_callback.dyn_into::<js_sys::Function>().ok();
};
|format_list_response_callback: JsValue| {
self.0.borrow_mut().format_list_response_callback = format_list_response_callback.dyn_into::<js_sys::Function>().ok();
};
|print_job_stream_callbacks: JsValue| {
let mut inner = self.0.borrow_mut();
match parse_print_job_stream_callbacks(print_job_stream_callbacks) {
Expand Down Expand Up @@ -341,6 +346,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
lock_callback,
unlock_callback,
locks_expired_callback,
format_list_response_callback,
invalid_print_job_stream_callbacks,
print_job_stream_callbacks,
printer_name,
Expand Down Expand Up @@ -381,6 +387,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
lock_callback = inner.lock_callback.clone();
unlock_callback = inner.unlock_callback.clone();
locks_expired_callback = inner.locks_expired_callback.clone();
format_list_response_callback = inner.format_list_response_callback.clone();
invalid_print_job_stream_callbacks = inner.invalid_print_job_stream_callbacks;
print_job_stream_callbacks = inner.print_job_stream_callbacks.clone();
printer_name = inner.printer_name.clone();
Expand Down Expand Up @@ -410,6 +417,7 @@ impl iron_remote_desktop::SessionBuilder for SessionBuilder {
on_lock: lock_callback,
on_unlock: unlock_callback,
on_locks_expired: locks_expired_callback,
on_format_list_response: format_list_response_callback,
},
)
});
Expand Down
Loading
Loading