mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-07 17:45:01 +00:00
refactor(web-client): refactor iron-remote-gui
into iron-remote-desktop
(#722)
This commit is contained in:
parent
184cfd24ae
commit
0ff1ed8de5
86 changed files with 4950 additions and 330 deletions
|
@ -168,12 +168,18 @@ WebAssembly high-level bindings targeting web browsers.
|
|||
|
||||
This crate is an **API Boundary** (WASM module).
|
||||
|
||||
#### [`web-client/iron-remote-gui`](./web-client/iron-remote-gui)
|
||||
#### [`web-client/iron-remote-desktop`](./web-client/iron-remote-desktop)
|
||||
|
||||
Core frontend UI used by `iron-svelte-client` as a Web Component.
|
||||
|
||||
This crate is an **API Boundary**.
|
||||
|
||||
#### [`web-client/iron-remote-desktop-rdp`](./web-client/iron-remote-desktop-rdp)
|
||||
|
||||
Implementation of the TypeScript interfaces exposed by WebAssembly bindings from `ironrdp-web` and used by `iron-svelte-client`.
|
||||
|
||||
This crate is an **API Boundary**.
|
||||
|
||||
#### [`web-client/iron-svelte-client`](./web-client/iron-svelte-client)
|
||||
|
||||
Web-based frontend using `Svelte` and `Material` frameworks.
|
||||
|
|
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -2809,6 +2809,8 @@ dependencies = [
|
|||
"resize",
|
||||
"rgb",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"smallvec",
|
||||
"softbuffer",
|
||||
"tap",
|
||||
|
@ -4668,6 +4670,17 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.17"
|
||||
|
|
|
@ -45,6 +45,8 @@ web-sys = { version = "0.3", features = ["HtmlCanvasElement"] }
|
|||
js-sys = "0.3"
|
||||
gloo-net = { version = "0.6", default-features = false, features = ["websocket", "http", "io-util"] }
|
||||
gloo-timers = { version = "0.3", default-features = false, features = ["futures"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6"
|
||||
tracing-web = "0.1"
|
||||
|
||||
# Rendering
|
||||
|
|
|
@ -137,7 +137,7 @@ impl WasmClipboard {
|
|||
pub(crate) fn new(message_proxy: WasmClipboardMessageProxy, js_callbacks: JsClipboardCallbacks) -> Self {
|
||||
Self {
|
||||
local_clipboard: None,
|
||||
remote_clipboard: ClipboardTransaction::new(),
|
||||
remote_clipboard: ClipboardTransaction::construct(),
|
||||
proxy: message_proxy,
|
||||
js_callbacks,
|
||||
|
||||
|
@ -505,7 +505,7 @@ impl WasmClipboard {
|
|||
} else {
|
||||
// If no initial clipboard callback was set, send empty format list instead
|
||||
return self.process_event(WasmClipboardBackendMessage::LocalClipboardChanged(
|
||||
ClipboardTransaction::new(),
|
||||
ClipboardTransaction::construct(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ impl ClipboardTransaction {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl ClipboardTransaction {
|
||||
pub fn new() -> Self {
|
||||
pub fn construct() -> Self {
|
||||
Self { contents: Vec::new() }
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use wasm_bindgen::prelude::*;
|
|||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IronRdpErrorKind {
|
||||
pub enum RemoteDesktopErrorKind {
|
||||
/// Catch-all error kind
|
||||
General,
|
||||
/// Incorrect password used
|
||||
|
@ -19,30 +19,30 @@ pub enum IronRdpErrorKind {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct IronRdpError {
|
||||
kind: IronRdpErrorKind,
|
||||
pub struct RemoteDesktopError {
|
||||
kind: RemoteDesktopErrorKind,
|
||||
source: anyhow::Error,
|
||||
}
|
||||
|
||||
impl IronRdpError {
|
||||
pub fn with_kind(mut self, kind: IronRdpErrorKind) -> Self {
|
||||
impl RemoteDesktopError {
|
||||
pub fn with_kind(mut self, kind: RemoteDesktopErrorKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl IronRdpError {
|
||||
impl RemoteDesktopError {
|
||||
pub fn backtrace(&self) -> String {
|
||||
format!("{:?}", self.source)
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> IronRdpErrorKind {
|
||||
pub fn kind(&self) -> RemoteDesktopErrorKind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl From<connector::ConnectorError> for IronRdpError {
|
||||
impl From<connector::ConnectorError> for RemoteDesktopError {
|
||||
fn from(e: connector::ConnectorError) -> Self {
|
||||
use sspi::credssp::NStatusCode;
|
||||
|
||||
|
@ -50,13 +50,13 @@ impl From<connector::ConnectorError> for IronRdpError {
|
|||
ConnectorErrorKind::Credssp(sspi::Error {
|
||||
nstatus: Some(NStatusCode::WRONG_PASSWORD),
|
||||
..
|
||||
}) => IronRdpErrorKind::WrongPassword,
|
||||
}) => RemoteDesktopErrorKind::WrongPassword,
|
||||
ConnectorErrorKind::Credssp(sspi::Error {
|
||||
nstatus: Some(NStatusCode::LOGON_FAILURE),
|
||||
..
|
||||
}) => IronRdpErrorKind::LogonFailure,
|
||||
ConnectorErrorKind::AccessDenied => IronRdpErrorKind::AccessDenied,
|
||||
_ => IronRdpErrorKind::General,
|
||||
}) => RemoteDesktopErrorKind::LogonFailure,
|
||||
ConnectorErrorKind::AccessDenied => RemoteDesktopErrorKind::AccessDenied,
|
||||
_ => RemoteDesktopErrorKind::General,
|
||||
};
|
||||
|
||||
Self {
|
||||
|
@ -66,19 +66,19 @@ impl From<connector::ConnectorError> for IronRdpError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ironrdp::session::SessionError> for IronRdpError {
|
||||
impl From<ironrdp::session::SessionError> for RemoteDesktopError {
|
||||
fn from(e: ironrdp::session::SessionError) -> Self {
|
||||
Self {
|
||||
kind: IronRdpErrorKind::General,
|
||||
kind: RemoteDesktopErrorKind::General,
|
||||
source: anyhow::Error::new(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for IronRdpError {
|
||||
impl From<anyhow::Error> for RemoteDesktopError {
|
||||
fn from(e: anyhow::Error) -> Self {
|
||||
Self {
|
||||
kind: IronRdpErrorKind::General,
|
||||
kind: RemoteDesktopErrorKind::General,
|
||||
source: e,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ pub struct DeviceEvent(pub(crate) Operation);
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceEvent {
|
||||
pub fn new_mouse_button_pressed(button: u8) -> Self {
|
||||
pub fn mouse_button_pressed(button: u8) -> Self {
|
||||
match MouseButton::from_web_button(button) {
|
||||
Some(button) => Self(Operation::MouseButtonPressed(button)),
|
||||
None => {
|
||||
|
@ -18,7 +18,7 @@ impl DeviceEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_mouse_button_released(button: u8) -> Self {
|
||||
pub fn mouse_button_released(button: u8) -> Self {
|
||||
match MouseButton::from_web_button(button) {
|
||||
Some(button) => Self(Operation::MouseButtonReleased(button)),
|
||||
None => {
|
||||
|
@ -28,30 +28,30 @@ impl DeviceEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_mouse_move(x: u16, y: u16) -> Self {
|
||||
pub fn mouse_move(x: u16, y: u16) -> Self {
|
||||
Self(Operation::MouseMove(MousePosition { x, y }))
|
||||
}
|
||||
|
||||
pub fn new_wheel_rotations(vertical: bool, rotation_units: i16) -> Self {
|
||||
pub fn wheel_rotations(vertical: bool, rotation_units: i16) -> Self {
|
||||
Self(Operation::WheelRotations(WheelRotations {
|
||||
is_vertical: vertical,
|
||||
rotation_units,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn new_key_pressed(scancode: u16) -> Self {
|
||||
pub fn key_pressed(scancode: u16) -> Self {
|
||||
Self(Operation::KeyPressed(Scancode::from_u16(scancode)))
|
||||
}
|
||||
|
||||
pub fn new_key_released(scancode: u16) -> Self {
|
||||
pub fn key_released(scancode: u16) -> Self {
|
||||
Self(Operation::KeyReleased(Scancode::from_u16(scancode)))
|
||||
}
|
||||
|
||||
pub fn new_unicode_pressed(unicode: char) -> Self {
|
||||
pub fn unicode_pressed(unicode: char) -> Self {
|
||||
Self(Operation::UnicodeKeyPressed(unicode))
|
||||
}
|
||||
|
||||
pub fn new_unicode_released(unicode: char) -> Self {
|
||||
pub fn unicode_released(unicode: char) -> Self {
|
||||
Self(Operation::UnicodeKeyReleased(unicode))
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ pub struct InputTransaction(pub(crate) SmallVec<[Operation; 3]>);
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl InputTransaction {
|
||||
pub fn new() -> Self {
|
||||
pub fn construct() -> Self {
|
||||
Self(SmallVec::new())
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ mod session;
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ironrdp_init(log_level: &str) {
|
||||
pub fn iron_init(log_level: &str) {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
|
@ -69,7 +69,7 @@ pub struct DesktopSize {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl DesktopSize {
|
||||
pub fn new(width: u16, height: u16) -> Self {
|
||||
pub fn construct(width: u16, height: u16) -> Self {
|
||||
DesktopSize { width, height }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDiscon
|
|||
use ironrdp_core::WriteBuf;
|
||||
use ironrdp_futures::{single_sequence_step_read, FramedWrite};
|
||||
use rgb::AsPixels as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tap::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
@ -36,7 +37,7 @@ use web_sys::HtmlCanvasElement;
|
|||
|
||||
use crate::canvas::Canvas;
|
||||
use crate::clipboard::{ClipboardTransaction, WasmClipboard, WasmClipboardBackend, WasmClipboardBackendMessage};
|
||||
use crate::error::{IronRdpError, IronRdpErrorKind};
|
||||
use crate::error::{RemoteDesktopError, RemoteDesktopErrorKind};
|
||||
use crate::image::extract_partial_image;
|
||||
use crate::input::InputTransaction;
|
||||
use crate::network_client::WasmNetworkClient;
|
||||
|
@ -102,7 +103,7 @@ impl Default for SessionBuilderInner {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl SessionBuilder {
|
||||
pub fn new() -> SessionBuilder {
|
||||
pub fn construct() -> SessionBuilder {
|
||||
Self(Rc::new(RefCell::new(SessionBuilderInner::default())))
|
||||
}
|
||||
|
||||
|
@ -146,18 +147,6 @@ impl SessionBuilder {
|
|||
self.clone()
|
||||
}
|
||||
|
||||
/// Optional
|
||||
pub fn pcb(&self, pcb: String) -> SessionBuilder {
|
||||
self.0.borrow_mut().pcb = Some(pcb);
|
||||
self.clone()
|
||||
}
|
||||
|
||||
/// Optional
|
||||
pub fn kdc_proxy_url(&self, kdc_proxy_url: Option<String>) -> SessionBuilder {
|
||||
self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url;
|
||||
self.clone()
|
||||
}
|
||||
|
||||
/// Optional
|
||||
pub fn desktop_size(&self, desktop_size: DesktopSize) -> SessionBuilder {
|
||||
self.0.borrow_mut().desktop_size = desktop_size;
|
||||
|
@ -216,13 +205,22 @@ impl SessionBuilder {
|
|||
self.clone()
|
||||
}
|
||||
|
||||
/// Optional
|
||||
pub fn use_display_control(&self) -> SessionBuilder {
|
||||
self.0.borrow_mut().use_display_control = true;
|
||||
pub fn extension(&self, value: JsValue) -> SessionBuilder {
|
||||
match serde_wasm_bindgen::from_value::<Extension>(value) {
|
||||
Ok(value) => match value {
|
||||
Extension::KdcProxyUrl(kdc_proxy_url) => self.0.borrow_mut().kdc_proxy_url = Some(kdc_proxy_url),
|
||||
Extension::Pcb(pcb) => self.0.borrow_mut().pcb = Some(pcb),
|
||||
Extension::DisplayControl(use_display_control) => {
|
||||
self.0.borrow_mut().use_display_control = use_display_control
|
||||
}
|
||||
},
|
||||
Err(error) => error!(%error, "Unsupported extension value"),
|
||||
}
|
||||
|
||||
self.clone()
|
||||
}
|
||||
|
||||
pub async fn connect(&self) -> Result<Session, IronRdpError> {
|
||||
pub async fn connect(&self) -> Result<Session, RemoteDesktopError> {
|
||||
let (
|
||||
username,
|
||||
destination,
|
||||
|
@ -297,11 +295,11 @@ impl SessionBuilder {
|
|||
loop {
|
||||
match ws.state() {
|
||||
websocket::State::Closing | websocket::State::Closed => {
|
||||
return Err(IronRdpError::from(anyhow::anyhow!(
|
||||
"Failed to connect to {proxy_address} (WebSocket is `{:?}`)",
|
||||
return Err(RemoteDesktopError::from(anyhow::anyhow!(
|
||||
"failed to connect to {proxy_address} (WebSocket is `{:?}`)",
|
||||
ws.state()
|
||||
))
|
||||
.with_kind(IronRdpErrorKind::ProxyConnect));
|
||||
.with_kind(RemoteDesktopErrorKind::ProxyConnect));
|
||||
}
|
||||
websocket::State::Connecting => {
|
||||
trace!("WebSocket is connecting to proxy at {proxy_address}...");
|
||||
|
@ -354,6 +352,13 @@ impl SessionBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Extension {
|
||||
KdcProxyUrl(String),
|
||||
Pcb(String),
|
||||
DisplayControl(bool),
|
||||
}
|
||||
|
||||
pub(crate) type FastPathInputEvents = smallvec::SmallVec<[FastPathInputEvent; 2]>;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -412,7 +417,7 @@ pub struct Session {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl Session {
|
||||
pub async fn run(&self) -> Result<SessionTerminationInfo, IronRdpError> {
|
||||
pub async fn run(&self) -> Result<SessionTerminationInfo, RemoteDesktopError> {
|
||||
let rdp_reader = self
|
||||
.rdp_reader
|
||||
.borrow_mut()
|
||||
|
@ -707,17 +712,17 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), IronRdpError> {
|
||||
pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), RemoteDesktopError> {
|
||||
let inputs = self.input_database.borrow_mut().apply(transaction);
|
||||
self.h_send_inputs(inputs)
|
||||
}
|
||||
|
||||
pub fn release_all_inputs(&self) -> Result<(), IronRdpError> {
|
||||
pub fn release_all_inputs(&self) -> Result<(), RemoteDesktopError> {
|
||||
let inputs = self.input_database.borrow_mut().release_all();
|
||||
self.h_send_inputs(inputs)
|
||||
}
|
||||
|
||||
fn h_send_inputs(&self, inputs: smallvec::SmallVec<[FastPathInputEvent; 2]>) -> Result<(), IronRdpError> {
|
||||
fn h_send_inputs(&self, inputs: smallvec::SmallVec<[FastPathInputEvent; 2]>) -> Result<(), RemoteDesktopError> {
|
||||
if !inputs.is_empty() {
|
||||
trace!("Inputs: {inputs:?}");
|
||||
|
||||
|
@ -735,7 +740,7 @@ impl Session {
|
|||
num_lock: bool,
|
||||
caps_lock: bool,
|
||||
kana_lock: bool,
|
||||
) -> Result<(), IronRdpError> {
|
||||
) -> Result<(), RemoteDesktopError> {
|
||||
use ironrdp::pdu::input::fast_path::FastPathInput;
|
||||
|
||||
let event = ironrdp::input::synchronize_event(scroll_lock, num_lock, caps_lock, kana_lock);
|
||||
|
@ -750,7 +755,7 @@ impl Session {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) -> Result<(), IronRdpError> {
|
||||
pub fn shutdown(&self) -> Result<(), RemoteDesktopError> {
|
||||
self.input_events_tx
|
||||
.unbounded_send(RdpInputEvent::TerminateSession)
|
||||
.context("failed to send terminate session event to writer task")?;
|
||||
|
@ -758,7 +763,7 @@ impl Session {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn on_clipboard_paste(&self, content: ClipboardTransaction) -> Result<(), IronRdpError> {
|
||||
pub async fn on_clipboard_paste(&self, content: ClipboardTransaction) -> Result<(), RemoteDesktopError> {
|
||||
self.input_events_tx
|
||||
.unbounded_send(RdpInputEvent::ClipboardBackend(
|
||||
WasmClipboardBackendMessage::LocalClipboardChanged(content),
|
||||
|
@ -768,7 +773,7 @@ impl Session {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) -> Result<(), IronRdpError> {
|
||||
fn set_cursor_style(&self, style: CursorStyle) -> Result<(), RemoteDesktopError> {
|
||||
let (kind, data, hotspot_x, hotspot_y) = match style {
|
||||
CursorStyle::Default => ("default", None, None, None),
|
||||
CursorStyle::Hidden => ("hidden", None, None, None),
|
||||
|
@ -818,6 +823,10 @@ impl Session {
|
|||
// plain scancode events are allowed to function correctly).
|
||||
false
|
||||
}
|
||||
|
||||
pub fn extension_call(_value: JsValue) -> Result<JsValue, RemoteDesktopError> {
|
||||
Ok(JsValue::null())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
|
@ -913,7 +922,7 @@ async fn connect(
|
|||
clipboard_backend,
|
||||
use_display_control,
|
||||
}: ConnectParams,
|
||||
) -> Result<(connector::ConnectionResult, WebSocket), IronRdpError> {
|
||||
) -> Result<(connector::ConnectionResult, WebSocket), RemoteDesktopError> {
|
||||
let mut framed = ironrdp_futures::LocalFuturesFramed::new(ws);
|
||||
|
||||
let mut connector = ClientConnector::new(config);
|
||||
|
@ -960,7 +969,7 @@ async fn connect_rdcleanpath<S>(
|
|||
destination: String,
|
||||
proxy_auth_token: String,
|
||||
pcb: Option<String>,
|
||||
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), IronRdpError>
|
||||
) -> Result<(ironrdp_futures::Upgraded, Vec<u8>), RemoteDesktopError>
|
||||
where
|
||||
S: ironrdp_futures::FramedRead + FramedWrite,
|
||||
{
|
||||
|
@ -1039,10 +1048,10 @@ where
|
|||
server_addr,
|
||||
} => (x224_connection_response, server_cert_chain, server_addr),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(
|
||||
IronRdpError::from(anyhow::anyhow!("received an RDCleanPath error: {error}"))
|
||||
.with_kind(IronRdpErrorKind::RDCleanPath),
|
||||
);
|
||||
return Err(RemoteDesktopError::from(
|
||||
anyhow::Error::new(error).context("received an RDCleanPath error"),
|
||||
)
|
||||
.with_kind(RemoteDesktopErrorKind::RDCleanPath));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
IronRDP also supports the web browser as a first class target.
|
||||
|
||||
See the [iron-remote-gui](./iron-remote-gui) for the reusable Web Component, and [iron-svelte-client](./iron-svelte-client) for a demonstration.
|
||||
See the [iron-remote-desktop](./iron-remote-desktop) for the reusable Web Component, and [iron-svelte-client](./iron-svelte-client) for a demonstration.
|
||||
|
||||
Note that the demonstration client is not intended to be used in production as-is.
|
||||
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
||||
|
|
15
web-client/iron-remote-desktop-rdp/.prettierignore
Normal file
15
web-client/iron-remote-desktop-rdp/.prettierignore
Normal file
|
@ -0,0 +1,15 @@
|
|||
node_modules/
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
/package
|
||||
/build
|
||||
/static/bearcss
|
||||
/static/material-icons
|
||||
/dist
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
19
web-client/iron-remote-desktop-rdp/.prettierrc.yaml
Normal file
19
web-client/iron-remote-desktop-rdp/.prettierrc.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Prettier:
|
||||
# - https://prettier.io/docs/en/options
|
||||
---
|
||||
useTabs: false
|
||||
tabWidth: 4
|
||||
singleQuote: true
|
||||
semi: true
|
||||
trailingComma: all
|
||||
printWidth: 120
|
||||
|
||||
overrides:
|
||||
- files:
|
||||
- '*.yml'
|
||||
- '*.yaml'
|
||||
- '*.json'
|
||||
- '*.html'
|
||||
- '*.md'
|
||||
options:
|
||||
tabWidth: 2
|
23
web-client/iron-remote-desktop-rdp/README.md
Normal file
23
web-client/iron-remote-desktop-rdp/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Iron Remote Desktop RDP
|
||||
|
||||
This is implementation of `RemoteDesktopModule` interface from [iron-remote-desktop](../iron-remote-desktop) for RDP connection.
|
||||
|
||||
## Development
|
||||
|
||||
Make your modification in the source code then use [iron-svelte-client](../iron-svelte-client) to test.
|
||||
|
||||
## Build
|
||||
|
||||
Run `npm run build`
|
||||
|
||||
## Usage
|
||||
|
||||
As member of the Devolutions organization, you can import the Web Component from JFrog Artifactory by running the following npm command:
|
||||
|
||||
```shell
|
||||
$ npm install @devolutions/iron-remote-desktop-rdp
|
||||
```
|
||||
|
||||
Otherwise, you can run `npm install` targeting the `dist/` folder directly.
|
||||
|
||||
Import the `iron-remote-desktop-rdp.umd.cjs` from `node_modules/` folder.
|
80
web-client/iron-remote-desktop-rdp/eslint.config.mjs
Normal file
80
web-client/iron-remote-desktop-rdp/eslint.config.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||
import globals from 'globals';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import js from '@eslint/js';
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'**/*.cjs',
|
||||
'**/.DS_Store',
|
||||
'**/node_modules',
|
||||
'build',
|
||||
'package',
|
||||
'**/.env',
|
||||
'**/.env.*',
|
||||
'!**/.env.example',
|
||||
'**/pnpm-lock.yaml',
|
||||
'**/package-lock.json',
|
||||
'**/yarn.lock',
|
||||
],
|
||||
},
|
||||
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'),
|
||||
{
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
strict: 2,
|
||||
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/strict-boolean-expressions': [
|
||||
2,
|
||||
{
|
||||
allowString: false,
|
||||
allowNumber: false,
|
||||
},
|
||||
],
|
||||
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
3995
web-client/iron-remote-desktop-rdp/package-lock.json
generated
Normal file
3995
web-client/iron-remote-desktop-rdp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
51
web-client/iron-remote-desktop-rdp/package.json
Normal file
51
web-client/iron-remote-desktop-rdp/package.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "@devolutions/iron-remote-desktop-rdp",
|
||||
"author": "Nicolas Girot",
|
||||
"email": "ngirot@devolutions.net",
|
||||
"contributors": [
|
||||
"Benoit Cortier",
|
||||
"Irving Ou",
|
||||
"Vladislav Nikonov",
|
||||
"Zacharia Ellaham",
|
||||
"Alexandr Yusuk"
|
||||
],
|
||||
"description": "Web Component providing agnostic implementation for Iron Wasm base client",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run pre-build && vite",
|
||||
"build": "npm run pre-build && vite build",
|
||||
"build-alone": "vite build",
|
||||
"pre-build": "node ./pre-build.js",
|
||||
"preview": "vite preview",
|
||||
"check": "tsc --noEmit",
|
||||
"check:dist": "tsc ./dist/index.d.ts --noEmit",
|
||||
"check:watch": "tsc --watch --noEmit",
|
||||
"lint": "npm run lint:prettier && npm run lint:eslint",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint:eslint": "eslint src/**",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.2.0",
|
||||
"vite-plugin-dts": "^4.5.0",
|
||||
"vite-plugin-top-level-await": "^1.2.2",
|
||||
"vite-plugin-wasm": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": "^6.6.7",
|
||||
"ua-parser-js": "^1.0.33"
|
||||
}
|
||||
}
|
34
web-client/iron-remote-desktop-rdp/pre-build.js
Normal file
34
web-client/iron-remote-desktop-rdp/pre-build.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { spawn } from 'child_process';
|
||||
|
||||
const run = async (command, cwd) => {
|
||||
try {
|
||||
const buildCommand = spawn(command, {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
cwd: cwd,
|
||||
});
|
||||
|
||||
buildCommand.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
|
||||
buildCommand.stderr.on('data', (data) => {
|
||||
console.error(`${data}`);
|
||||
});
|
||||
|
||||
const exitCode = await new Promise((resolve, reject) => {
|
||||
buildCommand.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Process exited with non-zero code: ${code}`));
|
||||
}
|
||||
resolve(code);
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`Child process exited with code: ${exitCode}`);
|
||||
} catch (err) {
|
||||
console.error(`Process run failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
await run('cargo xtask web build', '../../');
|
22
web-client/iron-remote-desktop-rdp/public/package.json
Normal file
22
web-client/iron-remote-desktop-rdp/public/package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@devolutions/iron-remote-desktop-rdp",
|
||||
"author": "Nicolas Girot",
|
||||
"email": "ngirot@devolutions.net",
|
||||
"contributors": [
|
||||
"Benoit Cortier",
|
||||
"Irving Ou",
|
||||
"Vladislav Nikonov",
|
||||
"Zacharia Ellaham"
|
||||
],
|
||||
"description": "Web Component providing agnostic implementation for Iron Wasm base client.",
|
||||
"version": "0.13.1",
|
||||
"main": "iron-remote-desktop-rdp.js",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"iron-remote-desktop-rdp.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"dependencies": {
|
||||
"rxjs": "^6.6.7"
|
||||
}
|
||||
}
|
26
web-client/iron-remote-desktop-rdp/src/main.ts
Normal file
26
web-client/iron-remote-desktop-rdp/src/main.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import init, {
|
||||
iron_init,
|
||||
DesktopSize,
|
||||
DeviceEvent,
|
||||
InputTransaction,
|
||||
RemoteDesktopError,
|
||||
Session,
|
||||
SessionBuilder,
|
||||
SessionTerminationInfo,
|
||||
ClipboardTransaction,
|
||||
ClipboardContent,
|
||||
} from '../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||
|
||||
export default {
|
||||
init,
|
||||
iron_init,
|
||||
DesktopSize,
|
||||
DeviceEvent,
|
||||
InputTransaction,
|
||||
RemoteDesktopError,
|
||||
SessionBuilder,
|
||||
ClipboardTransaction,
|
||||
ClipboardContent,
|
||||
Session,
|
||||
SessionTerminationInfo,
|
||||
};
|
1
web-client/iron-remote-desktop-rdp/src/vite-env.d.ts
vendored
Normal file
1
web-client/iron-remote-desktop-rdp/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
30
web-client/iron-remote-desktop-rdp/tsconfig.json
Normal file
30
web-client/iron-remote-desktop-rdp/tsconfig.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": false,
|
||||
"checkJs": false,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
25
web-client/iron-remote-desktop-rdp/vite.config.ts
Normal file
25
web-client/iron-remote-desktop-rdp/vite.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
import dtsPlugin from 'vite-plugin-dts';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: './src/main.ts',
|
||||
name: 'IronRemoteDesktopRdp',
|
||||
formats: ['es'],
|
||||
},
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
strict: false,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
topLevelAwait(),
|
||||
dtsPlugin({
|
||||
rollupTypes: true,
|
||||
}),
|
||||
],
|
||||
});
|
27
web-client/iron-remote-desktop/.gitignore
vendored
Normal file
27
web-client/iron-remote-desktop/.gitignore
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
1
web-client/iron-remote-desktop/.npmrc
Normal file
1
web-client/iron-remote-desktop/.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
|
@ -1,16 +1,16 @@
|
|||
node_modules/
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
/.svelte-kit
|
||||
/package
|
||||
/build
|
||||
/static/bearcss
|
||||
/static/material-icons
|
||||
/dist
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
node_modules/
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
/.svelte-kit
|
||||
/package
|
||||
/build
|
||||
/static/bearcss
|
||||
/static/material-icons
|
||||
/dist
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
|
@ -1,6 +1,7 @@
|
|||
# Iron Remote GUI
|
||||
# Iron Remote Desktop
|
||||
|
||||
This is the core of the web client written on top of Svelte and built as a reusable Web Component.
|
||||
Also, it contains the TypeScript interfaces exposed by WebAssembly bindings from `ironrdp-web` and used by `iron-svelte-client`.
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -15,17 +16,17 @@ Run `npm run build`
|
|||
As member of the Devolutions organization, you can import the Web Component from JFrog Artifactory by running the following npm command:
|
||||
|
||||
```shell
|
||||
$ npm install @devolutions/iron-remote-gui
|
||||
$ npm install @devolutions/iron-remote-desktop
|
||||
```
|
||||
|
||||
Otherwise, you can run `npm install` targeting the `dist/` folder directly.
|
||||
|
||||
Import the `iron-remote-gui.umd.cjs` from `node_modules/` folder.
|
||||
Import the `iron-remote-desktop.umd.cjs` from `node_modules/` folder.
|
||||
|
||||
Then use the HTML tag `<iron-remote-gui/>` in your page.
|
||||
Then use the HTML tag `<iron-remote-desktop/>` in your page.
|
||||
|
||||
In your code add a listener for the `ready` event on the `iron-remote-gui` HTML element.
|
||||
Get `evt.detail.irgUserInteraction` from the `Promise`, a property whose type is `UserInteractionService`.
|
||||
In your code add a listener for the `ready` event on the `iron-remote-desktop` HTML element.
|
||||
Get `evt.detail.irgUserInteraction` from the `Promise`, a property whose type is `UserInteraction`.
|
||||
Call the `connect` method on this object.
|
||||
|
||||
## Limitations
|
||||
|
@ -35,22 +36,23 @@ You need to recreate them on your application for now (it will be improved in fu
|
|||
|
||||
Also, even if the connection to RDP work there is still a lot of improvement to do.
|
||||
As of now, you can expect, mouse movement and click (4 buttons) - no scroll, Keyboard for at least the standard.
|
||||
Windows and CTRL+ALT+DEL can be called by method on `UserInteractionService`.
|
||||
Windows and CTRL+ALT+DEL can be called by method on `UserInteraction`.
|
||||
Lock keys (like caps lock), have a partial support.
|
||||
Other advanced functionalities (sharing / copy past...) are not implemented yet.
|
||||
|
||||
## Component parameters
|
||||
|
||||
You can add some parameters for default initialization on the component `<iron-remote-gui />`.
|
||||
You can add some parameters for default initialization on the component `<iron-remote-desktop />`.
|
||||
|
||||
> Note that due to a limitation of the framework all parameters need to be lower-cased.
|
||||
|
||||
- `scale`: The scaling behavior of the distant screen. Can be `fit`, `real` or `full`. Default is `real`;
|
||||
- `verbose`: Show logs from `iron-remote-gui`. `true` or `false`. Default is `false`.
|
||||
- `verbose`: Show logs from `iron-remote-desktop`. `true` or `false`. Default is `false`.
|
||||
- `debugwasm`: Show debug info from web assembly. Can be `"OFF"`, `"ERROR"`, `"WARN"`, `"INFO"`, `"DEBUG"`, `"TRACE"`. Default is `"OFF"`.
|
||||
- `flexcentre`: Helper to force `iron-remote-gui` a flex and centering the content automatically. Otherwise, you need to manage manually. Default is `true`.
|
||||
- `flexcentre`: Helper to force `iron-remote-desktop` a flex and centering the content automatically. Otherwise, you need to manage manually. Default is `true`.
|
||||
- `module`: An implementation of the [RemoteDesktopModule](./src/interfaces/RemoteDesktopModule.ts)
|
||||
|
||||
## `UserInteractionService` methods
|
||||
## `UserInteraction` methods
|
||||
|
||||
```ts
|
||||
connect(
|
||||
|
@ -63,6 +65,7 @@ connect(
|
|||
desktopSize?: DesktopSize,
|
||||
preConnectionBlob?: string,
|
||||
kdc_proxy_url?: string,
|
||||
use_display_control: boolean,
|
||||
): Observable<NewSessionInfo>;
|
||||
```
|
||||
|
||||
|
@ -70,12 +73,20 @@ connect(
|
|||
|
||||
> `destination` refers to the Devolutions Gateway hostname and port.
|
||||
|
||||
> `authtoken` is the authentication token to send to the Devolutions Gateway.
|
||||
> `proxyAddress` is the address of the Devolutions Gateway proxy
|
||||
|
||||
> `serverDomain` is the Windows domain name (if the target computer has one)
|
||||
|
||||
> `authtoken` is the authentication token to send to the Devolutions Gateway.
|
||||
|
||||
> `desktopSize` is the initial size of the desktop
|
||||
|
||||
> `preConnectionBlob` is the pre connection blob data
|
||||
|
||||
> `kdc_proxy_url` is the URL to a KDC Proxy, as specified in [MS-KKDCP documentation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kkdcp/5bcebb8d-b747-4ee5-9453-428aec1c5c38)
|
||||
|
||||
> `use_display_control` is the value that defined if the Display Control Virtual Channel will be used.
|
||||
|
||||
> `ctrlAltDel()`
|
||||
>
|
||||
> Sends the ctrl+alt+del key to server.
|
||||
|
@ -84,11 +95,31 @@ connect(
|
|||
>
|
||||
> Sends the meta key event to remote host (i.e.: Windows key).
|
||||
|
||||
> `setVisibility(value: bool)`
|
||||
> `setVisibility(value: boolean)`
|
||||
>
|
||||
> Shows or hides rendering canvas.
|
||||
|
||||
> `setScale(scale: ScreenScale)`
|
||||
>
|
||||
> Sets the scale behavior of the canvas.
|
||||
> See the [ScreenScale](./src/services/user-interaction-service.ts) enum for possible values.
|
||||
> See the [ScreenScale](./src/enums/ScreenScale.ts) enum for possible values.
|
||||
|
||||
> `shutdown()`
|
||||
>
|
||||
> Shutdowns the active session.
|
||||
|
||||
> `setKeyboardUnicodeMode(use_unicode: boolean)`
|
||||
>
|
||||
> Sets the keyboard Unicode mode.
|
||||
|
||||
> `setCursorStyleOverride(style?: string)`
|
||||
>
|
||||
> Overrides the default cursor style. If `style` is `null`, the default cursor style will be used.
|
||||
|
||||
> `resize(width: number, height: number, scale?: number)`
|
||||
>
|
||||
> Resizes the screen.
|
||||
|
||||
> `setEnableClipboard(enable: boolean)`
|
||||
>
|
||||
> Enables or disable the clipboard based on the `enable` value.
|
|
@ -7,10 +7,10 @@
|
|||
</head>
|
||||
<body>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<iron-remote-gui isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||
<iron-remote-desktop isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||
|
||||
<script>
|
||||
var el = document.querySelector('iron-remote-gui');
|
||||
var el = document.querySelector('iron-remote-desktop');
|
||||
el.addEventListener('ready', (e) => {
|
||||
console.log('WebComponent Loaded');
|
||||
e.detail.irgUserInteraction.setVisibility(true);
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "@devolutions/iron-remote-gui",
|
||||
"name": "@devolutions/iron-remote-desktop",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@devolutions/iron-remote-gui",
|
||||
"name": "@devolutions/iron-remote-desktop",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"rxjs": "^6.6.7",
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@devolutions/iron-remote-gui",
|
||||
"name": "@devolutions/iron-remote-desktop",
|
||||
"author": "Nicolas Girot",
|
||||
"email": "ngirot@devolutions.net",
|
||||
"contributors": [
|
||||
|
@ -13,10 +13,9 @@
|
|||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run pre-build && vite",
|
||||
"build": "npm run pre-build && vite build",
|
||||
"dev": "npm run vite",
|
||||
"build": "npm run vite build",
|
||||
"build-alone": "vite build",
|
||||
"pre-build": "node ./pre-build.js",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:dist": "tsc ./dist/index.d.ts --noEmit",
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@devolutions/iron-remote-gui",
|
||||
"name": "@devolutions/iron-remote-desktop",
|
||||
"author": "Nicolas Girot",
|
||||
"email": "ngirot@devolutions.net",
|
||||
"contributors": [
|
||||
|
@ -10,10 +10,10 @@
|
|||
],
|
||||
"description": "Web Component providing agnostic implementation for Iron Wasm base client.",
|
||||
"version": "0.13.1",
|
||||
"main": "iron-remote-gui.js",
|
||||
"main": "iron-remote-desktop.js",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"iron-remote-gui.js",
|
||||
"iron-remote-desktop.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"dependencies": {
|
|
@ -6,11 +6,11 @@
|
|||
<title>Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./iron-remote-gui.js"></script>
|
||||
<iron-remote-gui isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||
<script type="module" src="./iron-remote-desktop.js"></script>
|
||||
<iron-remote-desktop isvisible="false" desktopwidth="600" desktopheight="400" />
|
||||
|
||||
<script>
|
||||
var el = document.querySelector('iron-remote-gui');
|
||||
var el = document.querySelector('iron-remote-desktop');
|
||||
el.addEventListener('ready', (e) => {
|
||||
e.detail.irgUserInteraction.setVisibility(true);
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
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,8 @@
|
|||
import type { ClipboardContent } from './ClipboardContent';
|
||||
|
||||
export interface ClipboardTransaction {
|
||||
construct(): ClipboardTransaction;
|
||||
add_content(content: ClipboardContent): void;
|
||||
is_empty(): boolean;
|
||||
content(): Array<ClipboardContent>;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
export interface DesktopSize {
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
construct(width: number, height: number): DesktopSize;
|
||||
}
|
10
web-client/iron-remote-desktop/src/interfaces/DeviceEvent.ts
Normal file
10
web-client/iron-remote-desktop/src/interfaces/DeviceEvent.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export interface DeviceEvent {
|
||||
mouse_button_pressed(button: number): DeviceEvent;
|
||||
mouse_button_released(button: number): DeviceEvent;
|
||||
mouse_move(x: number, y: number): DeviceEvent;
|
||||
wheel_rotations(vertical: boolean, rotation_units: number): DeviceEvent;
|
||||
key_pressed(scancode: number): DeviceEvent;
|
||||
key_released(scancode: number): DeviceEvent;
|
||||
unicode_pressed(unicode: string): DeviceEvent;
|
||||
unicode_released(unicode: string): DeviceEvent;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import type { DeviceEvent } from './DeviceEvent';
|
||||
|
||||
export interface InputTransaction {
|
||||
construct(): InputTransaction;
|
||||
add_event(event: DeviceEvent): void;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import type { DesktopSize } from './DesktopSize';
|
||||
import type { DeviceEvent } from './DeviceEvent';
|
||||
import type { InputTransaction } from './InputTransaction';
|
||||
import type { RemoteDesktopError } 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';
|
||||
|
||||
export interface RemoteDesktopModule {
|
||||
init: () => Promise<unknown>;
|
||||
iron_init: (logLevel: string) => void;
|
||||
DesktopSize: DesktopSize;
|
||||
DeviceEvent: DeviceEvent;
|
||||
InputTransaction: InputTransaction;
|
||||
RemoteDesktopError: RemoteDesktopError;
|
||||
Session: Session;
|
||||
SessionBuilder: SessionBuilder;
|
||||
SessionTerminationInfo: SessionTerminationInfo;
|
||||
ClipboardTransaction: ClipboardTransaction;
|
||||
ClipboardContent: ClipboardContent;
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
export interface ServerRect {
|
||||
free(): void;
|
||||
|
||||
clone_buffer(): Uint8Array;
|
||||
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
|
||||
clone_buffer(): Uint8Array;
|
||||
}
|
23
web-client/iron-remote-desktop/src/interfaces/Session.ts
Normal file
23
web-client/iron-remote-desktop/src/interfaces/Session.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { InputTransaction } from './InputTransaction';
|
||||
import type { DesktopSize } from './DesktopSize';
|
||||
import type { SessionTerminationInfo } from './SessionTerminationInfo';
|
||||
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||
|
||||
export interface Session {
|
||||
run(): Promise<SessionTerminationInfo>;
|
||||
desktop_size(): DesktopSize;
|
||||
apply_inputs(transaction: InputTransaction): void;
|
||||
release_all_inputs(): void;
|
||||
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>;
|
||||
resize(
|
||||
width: number,
|
||||
height: number,
|
||||
scale_factor?: number | null,
|
||||
physical_width?: number | null,
|
||||
physical_height?: number | null,
|
||||
): void;
|
||||
supports_unicode_keyboard_shortcuts(): boolean;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import type { Session } from './Session';
|
||||
import type { DesktopSize } from './DesktopSize';
|
||||
import type { ClipboardTransaction } from './ClipboardTransaction';
|
||||
|
||||
export interface SessionBuilder {
|
||||
construct(): SessionBuilder;
|
||||
/**
|
||||
* Required
|
||||
*/
|
||||
username(username: string): SessionBuilder;
|
||||
/**
|
||||
* Required
|
||||
*/
|
||||
destination(destination: string): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
server_domain(server_domain: string): SessionBuilder;
|
||||
/**
|
||||
* Required
|
||||
*/
|
||||
password(password: string): SessionBuilder;
|
||||
/**
|
||||
* Required
|
||||
*/
|
||||
proxy_address(address: string): SessionBuilder;
|
||||
/**
|
||||
* Required
|
||||
*/
|
||||
auth_token(token: string): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
desktop_size(desktop_size: DesktopSize): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
render_canvas(canvas: HTMLCanvasElement): SessionBuilder;
|
||||
/**
|
||||
* Required.
|
||||
*
|
||||
* # Cursor kinds:
|
||||
* - `default` (default system cursor); other arguments are `UNDEFINED`
|
||||
* - `none` (hide cursor); other arguments are `UNDEFINED`
|
||||
* - `url` (custom cursor data URL); `cursor_data` contains the data URL with Base64-encoded
|
||||
* cursor bitmap; `hotspot_x` and `hotspot_y` are set to the cursor hotspot coordinates.
|
||||
*/
|
||||
set_cursor_style_callback(callback: SetCursorStyleCallback): SessionBuilder;
|
||||
/**
|
||||
* Required.
|
||||
*/
|
||||
set_cursor_style_callback_context(context: unknown): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
remote_clipboard_changed_callback(callback: RemoteClipboardChangedCallback): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
remote_received_format_list_callback(callback: RemoteReceiveForwardListCallback): SessionBuilder;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
force_clipboard_update_callback(callback: ForceClipboardUpdateCallback): SessionBuilder;
|
||||
extension(value: unknown): SessionBuilder;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
extension_call(_ident: string, _call: Function): SessionBuilder;
|
||||
connect(): Promise<Session>;
|
||||
}
|
||||
|
||||
interface SetCursorStyleCallback {
|
||||
(
|
||||
cursor_kind: string,
|
||||
cursor_data: string | undefined,
|
||||
hotspot_x: number | undefined,
|
||||
hotspot_y: number | undefined,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface RemoteClipboardChangedCallback {
|
||||
(transaction: ClipboardTransaction): void;
|
||||
}
|
||||
|
||||
interface RemoteReceiveForwardListCallback {
|
||||
(): void;
|
||||
}
|
||||
|
||||
interface ForceClipboardUpdateCallback {
|
||||
(): void;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface SessionTerminationInfo {
|
||||
reason(): string;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import type { SessionEventType } from '../enums/SessionEventType';
|
||||
|
||||
export enum UserIronRdpErrorKind {
|
||||
export enum RemoteDesktopErrorKind {
|
||||
General = 0,
|
||||
WrongPassword = 1,
|
||||
LogonFailure = 2,
|
||||
|
@ -8,12 +8,12 @@ export enum UserIronRdpErrorKind {
|
|||
RDCleanPath = 4,
|
||||
ProxyConnect = 5,
|
||||
}
|
||||
export interface UserIronRdpError {
|
||||
export interface RemoteDesktopError {
|
||||
backtrace: () => string;
|
||||
kind: () => UserIronRdpErrorKind;
|
||||
kind: () => RemoteDesktopErrorKind;
|
||||
}
|
||||
|
||||
export interface SessionEvent {
|
||||
type: SessionEventType;
|
||||
data: UserIronRdpError | string;
|
||||
data: RemoteDesktopError | string;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<svelte:options
|
||||
customElement={{
|
||||
tag: 'iron-remote-gui',
|
||||
tag: 'iron-remote-desktop',
|
||||
shadow: 'none',
|
||||
extend: (elementConstructor) => {
|
||||
return class extends elementConstructor {
|
||||
|
@ -16,23 +16,26 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { loggingService } from './services/logging.service';
|
||||
import { WasmBridgeService } from './services/wasm-bridge.service';
|
||||
import { RemoteDesktopService } from './services/remote-desktop.service';
|
||||
import { LogType } from './enums/LogType';
|
||||
import type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||
import { PublicAPI } from './services/PublicAPI';
|
||||
import { ScreenScale } from './enums/ScreenScale';
|
||||
import { ClipboardContent, ClipboardTransaction } from '../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||
import type { ClipboardTransaction } from './interfaces/ClipboardTransaction';
|
||||
import type { RemoteDesktopModule } from './interfaces/RemoteDesktopModule';
|
||||
|
||||
let {
|
||||
scale,
|
||||
verbose,
|
||||
debugwasm,
|
||||
flexcenter,
|
||||
module,
|
||||
}: {
|
||||
scale: string;
|
||||
verbose: 'true' | 'false';
|
||||
debugwasm: 'OFF' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
|
||||
flexcenter: string;
|
||||
module: RemoteDesktopModule;
|
||||
} = $props();
|
||||
|
||||
let isVisible = $state(false);
|
||||
|
@ -51,9 +54,8 @@
|
|||
|
||||
let viewerStyle = $state('');
|
||||
let wrapperStyle = $state('');
|
||||
|
||||
let wasmService = new WasmBridgeService();
|
||||
let publicAPI = new PublicAPI(wasmService);
|
||||
let remoteDesktopService = new RemoteDesktopService(module);
|
||||
let publicAPI = new PublicAPI(remoteDesktopService);
|
||||
|
||||
// Firefox's clipboard API is very limited, and doesn't support reading from the clipboard
|
||||
// without changing browser settings via `about:config`.
|
||||
|
@ -102,12 +104,12 @@
|
|||
}
|
||||
|
||||
if (isFirefox) {
|
||||
wasmService.setOnRemoteClipboardChanged(ffOnRemoteClipboardChanged);
|
||||
wasmService.setOnRemoteReceivedFormatList(ffOnRemoteReceivedFormatList);
|
||||
wasmService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||
remoteDesktopService.setOnRemoteClipboardChanged(ffOnRemoteClipboardChanged);
|
||||
remoteDesktopService.setOnRemoteReceivedFormatList(ffOnRemoteReceivedFormatList);
|
||||
remoteDesktopService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||
} else if (isClipboardApiSupported) {
|
||||
wasmService.setOnRemoteClipboardChanged(onRemoteClipboardChanged);
|
||||
wasmService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||
remoteDesktopService.setOnRemoteClipboardChanged(onRemoteClipboardChanged);
|
||||
remoteDesktopService.setOnForceClipboardUpdate(onForceClipboardUpdate);
|
||||
|
||||
// Start the clipboard monitoring loop
|
||||
setTimeout(onMonitorClipboard, CLIPBOARD_MONITORING_INTERVAL);
|
||||
|
@ -135,12 +137,9 @@
|
|||
let result = {} as Record<string, Blob>;
|
||||
|
||||
for (const item of transaction.content()) {
|
||||
if (!(item instanceof ClipboardContent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mime = item.mime_type();
|
||||
let value = new Blob([item.value()], { type: mime });
|
||||
|
||||
result[mime] = value;
|
||||
}
|
||||
|
||||
|
@ -151,9 +150,9 @@
|
|||
function onForceClipboardUpdate() {
|
||||
try {
|
||||
if (lastClientClipboardTransaction) {
|
||||
wasmService.onClipboardChanged(lastClientClipboardTransaction);
|
||||
remoteDesktopService.onClipboardChanged(lastClientClipboardTransaction);
|
||||
} else {
|
||||
wasmService.onClipboardChanged(ClipboardTransaction.new());
|
||||
remoteDesktopService.onClipboardChangedEmpty();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to send initial clipboard state: ' + err);
|
||||
|
@ -237,7 +236,7 @@
|
|||
if (!sameValue) {
|
||||
lastClientClipboardItems = values;
|
||||
|
||||
let transaction = ClipboardTransaction.new();
|
||||
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||
|
||||
// Iterate over `Record` type
|
||||
values.forEach((value: string | Uint8Array, key: string) => {
|
||||
|
@ -247,15 +246,15 @@
|
|||
}
|
||||
|
||||
if (key.startsWith('text/') && typeof value === 'string') {
|
||||
transaction.add_content(ClipboardContent.new_text(key, value));
|
||||
transaction.add_content(remoteDesktopService.constructClipboardContentFromText(key, value));
|
||||
} else if (key.startsWith('image/') && value instanceof Uint8Array) {
|
||||
transaction.add_content(ClipboardContent.new_binary(key, value));
|
||||
transaction.add_content(remoteDesktopService.constructClipboardContentFromBinary(key, value));
|
||||
}
|
||||
});
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
lastClientClipboardTransaction = transaction;
|
||||
wasmService.onClipboardChanged(transaction);
|
||||
remoteDesktopService.onClipboardChanged(transaction);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -335,7 +334,7 @@
|
|||
}
|
||||
|
||||
try {
|
||||
let transaction = ClipboardTransaction.new();
|
||||
let transaction = remoteDesktopService.constructClipboardTransaction();
|
||||
|
||||
if (evt.clipboardData == null) {
|
||||
return;
|
||||
|
@ -346,11 +345,11 @@
|
|||
|
||||
if (mime.startsWith('text/')) {
|
||||
clipItem.getAsString((str: string) => {
|
||||
let content = ClipboardContent.new_text(mime, str);
|
||||
let content = remoteDesktopService.constructClipboardContentFromText(mime, str);
|
||||
transaction.add_content(content);
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
wasmService.onClipboardChanged(transaction);
|
||||
remoteDesktopService.onClipboardChanged(transaction as ClipboardTransaction);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -364,11 +363,11 @@
|
|||
|
||||
file.arrayBuffer().then((buffer: ArrayBuffer) => {
|
||||
const strict_buffer = new Uint8Array(buffer);
|
||||
let content = ClipboardContent.new_binary(mime, strict_buffer);
|
||||
let content = remoteDesktopService.constructClipboardContentFromBinary(mime, strict_buffer);
|
||||
transaction.add_content(content);
|
||||
|
||||
if (!transaction.is_empty()) {
|
||||
wasmService.onClipboardChanged(transaction);
|
||||
remoteDesktopService.onClipboardChanged(transaction);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -454,7 +453,7 @@
|
|||
}
|
||||
|
||||
function serverBridgeListeners() {
|
||||
wasmService.resize.subscribe((evt: ResizeEvent) => {
|
||||
remoteDesktopService.resize.subscribe((evt: ResizeEvent) => {
|
||||
loggingService.info(`Resize canvas to: ${evt.desktop_size.width}x${evt.desktop_size.height}`);
|
||||
canvas.width = evt.desktop_size.width;
|
||||
canvas.height = evt.desktop_size.height;
|
||||
|
@ -467,17 +466,17 @@
|
|||
scaleSession(scale);
|
||||
});
|
||||
|
||||
wasmService.scaleObserver.subscribe((s) => {
|
||||
remoteDesktopService.scaleObserver.subscribe((s) => {
|
||||
loggingService.info('Change scale!');
|
||||
scaleSession(s);
|
||||
});
|
||||
|
||||
wasmService.dynamicResize.subscribe((evt) => {
|
||||
remoteDesktopService.dynamicResize.subscribe((evt) => {
|
||||
loggingService.info(`Dynamic resize!, width: ${evt.width}, height: ${evt.height}`);
|
||||
setViewerStyle(evt.width.toString(), evt.height.toString(), true);
|
||||
});
|
||||
|
||||
wasmService.changeVisibilityObservable.subscribe((val) => {
|
||||
remoteDesktopService.changeVisibilityObservable.subscribe((val) => {
|
||||
isVisible = val;
|
||||
if (val) {
|
||||
//Enforce first scaling and delay the call to scaleSession to ensure Dom is ready.
|
||||
|
@ -590,7 +589,7 @@
|
|||
y: Math.round((evt.clientY - rect.top) * scaleY),
|
||||
};
|
||||
|
||||
wasmService.updateMousePosition(coord);
|
||||
remoteDesktopService.updateMousePosition(coord);
|
||||
}
|
||||
|
||||
function setMouseButtonState(state: MouseEvent, isDown: boolean) {
|
||||
|
@ -610,20 +609,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
wasmService.mouseButtonState(state, isDown, true);
|
||||
remoteDesktopService.mouseButtonState(state, isDown, true);
|
||||
}
|
||||
|
||||
function mouseWheel(evt: WheelEvent) {
|
||||
wasmService.mouseWheel(evt);
|
||||
remoteDesktopService.mouseWheel(evt);
|
||||
}
|
||||
|
||||
function setMouseIn(evt: MouseEvent) {
|
||||
canvas.focus();
|
||||
wasmService.mouseIn(evt);
|
||||
remoteDesktopService.mouseIn(evt);
|
||||
}
|
||||
|
||||
function setMouseOut(evt: MouseEvent) {
|
||||
wasmService.mouseOut(evt);
|
||||
remoteDesktopService.mouseOut(evt);
|
||||
}
|
||||
|
||||
function keyboardEvent(evt: KeyboardEvent) {
|
||||
|
@ -639,7 +638,7 @@
|
|||
ffWaitForRemoteClipboardTransactionSet();
|
||||
}
|
||||
|
||||
wasmService.sendKeyboardEvent(evt);
|
||||
remoteDesktopService.sendKeyboardEvent(evt);
|
||||
|
||||
// Propagate further
|
||||
return true;
|
||||
|
@ -663,8 +662,8 @@
|
|||
canvas.height = 600;
|
||||
|
||||
const logLevel = LogType[debugwasm] ?? LogType.INFO;
|
||||
await wasmService.init(logLevel);
|
||||
wasmService.setCanvas(canvas);
|
||||
await remoteDesktopService.init(logLevel);
|
||||
remoteDesktopService.setCanvas(canvas);
|
||||
|
||||
initListeners();
|
||||
|
15
web-client/iron-remote-desktop/src/main.ts
Normal file
15
web-client/iron-remote-desktop/src/main.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export * as default from './iron-remote-desktop.svelte';
|
||||
export type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||
export type { NewSessionInfo } from './interfaces/NewSessionInfo';
|
||||
export type { ServerRect } from './interfaces/ServerRect';
|
||||
export type { DesktopSize } from './interfaces/DesktopSize';
|
||||
export type { SessionEvent, RemoteDesktopError, RemoteDesktopErrorKind } 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 { DeviceEvent } from './interfaces/DeviceEvent';
|
||||
export type { InputTransaction } from './interfaces/InputTransaction';
|
||||
export type { Session } from './interfaces/Session';
|
||||
export type { SessionBuilder } from './interfaces/SessionBuilder';
|
||||
export type { UserInteraction } from './interfaces/UserInteraction';
|
|
@ -1,16 +1,16 @@
|
|||
import { loggingService } from './logging.service';
|
||||
import type { NewSessionInfo } from '../interfaces/NewSessionInfo';
|
||||
import { SpecialCombination } from '../enums/SpecialCombination';
|
||||
import type { WasmBridgeService } from './wasm-bridge.service';
|
||||
import { RemoteDesktopService } from './remote-desktop.service';
|
||||
import type { UserInteraction } from '../interfaces/UserInteraction';
|
||||
import type { ScreenScale } from '../enums/ScreenScale';
|
||||
import type { DesktopSize } from '../interfaces/DesktopSize';
|
||||
|
||||
export class PublicAPI {
|
||||
private wasmService: WasmBridgeService;
|
||||
private remoteDesktopService: RemoteDesktopService;
|
||||
|
||||
constructor(wasmService: WasmBridgeService) {
|
||||
this.wasmService = wasmService;
|
||||
constructor(remoteDesktopService: RemoteDesktopService) {
|
||||
this.remoteDesktopService = remoteDesktopService;
|
||||
}
|
||||
|
||||
private connect(
|
||||
|
@ -26,7 +26,7 @@ export class PublicAPI {
|
|||
use_display_control = false,
|
||||
): Promise<NewSessionInfo> {
|
||||
loggingService.info('Initializing connection.');
|
||||
const resultObservable = this.wasmService.connect(
|
||||
const resultObservable = this.remoteDesktopService.connect(
|
||||
username,
|
||||
password,
|
||||
destination,
|
||||
|
@ -43,40 +43,40 @@ export class PublicAPI {
|
|||
}
|
||||
|
||||
private ctrlAltDel() {
|
||||
this.wasmService.sendSpecialCombination(SpecialCombination.CTRL_ALT_DEL);
|
||||
this.remoteDesktopService.sendSpecialCombination(SpecialCombination.CTRL_ALT_DEL);
|
||||
}
|
||||
|
||||
private metaKey() {
|
||||
this.wasmService.sendSpecialCombination(SpecialCombination.META);
|
||||
this.remoteDesktopService.sendSpecialCombination(SpecialCombination.META);
|
||||
}
|
||||
|
||||
private setVisibility(state: boolean) {
|
||||
loggingService.info(`Change component visibility to: ${state}`);
|
||||
this.wasmService.setVisibility(state);
|
||||
this.remoteDesktopService.setVisibility(state);
|
||||
}
|
||||
|
||||
private setScale(scale: ScreenScale) {
|
||||
this.wasmService.setScale(scale);
|
||||
this.remoteDesktopService.setScale(scale);
|
||||
}
|
||||
|
||||
private shutdown() {
|
||||
this.wasmService.shutdown();
|
||||
this.remoteDesktopService.shutdown();
|
||||
}
|
||||
|
||||
private setKeyboardUnicodeMode(use_unicode: boolean) {
|
||||
this.wasmService.setKeyboardUnicodeMode(use_unicode);
|
||||
this.remoteDesktopService.setKeyboardUnicodeMode(use_unicode);
|
||||
}
|
||||
|
||||
private setCursorStyleOverride(style: string | null) {
|
||||
this.wasmService.setCursorStyleOverride(style);
|
||||
this.remoteDesktopService.setCursorStyleOverride(style);
|
||||
}
|
||||
|
||||
private resize(width: number, height: number, scale?: number) {
|
||||
this.wasmService.resizeDynamic(width, height, scale);
|
||||
this.remoteDesktopService.resizeDynamic(width, height, scale);
|
||||
}
|
||||
|
||||
private setEnableClipboard(enable: boolean) {
|
||||
this.wasmService.setEnableClipboard(enable);
|
||||
this.remoteDesktopService.setEnableClipboard(enable);
|
||||
}
|
||||
|
||||
getExposedFunctions(): UserInteraction {
|
||||
|
@ -85,7 +85,7 @@ export class PublicAPI {
|
|||
connect: this.connect.bind(this),
|
||||
setScale: this.setScale.bind(this),
|
||||
onSessionEvent: (callback) => {
|
||||
this.wasmService.sessionObserver.subscribe(callback);
|
||||
this.remoteDesktopService.sessionObserver.subscribe(callback);
|
||||
},
|
||||
ctrlAltDel: this.ctrlAltDel.bind(this),
|
||||
metaKey: this.metaKey.bind(this),
|
|
@ -1,15 +1,4 @@
|
|||
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
|
||||
import init, {
|
||||
DesktopSize,
|
||||
DeviceEvent,
|
||||
InputTransaction,
|
||||
ironrdp_init,
|
||||
IronRdpError,
|
||||
Session,
|
||||
SessionBuilder,
|
||||
ClipboardTransaction,
|
||||
SessionTerminationInfo,
|
||||
} from '../../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||
import { loggingService } from './logging.service';
|
||||
import { catchError, filter, map } from 'rxjs/operators';
|
||||
import { scanCode } from '../lib/scancodes';
|
||||
|
@ -23,14 +12,21 @@ import { SpecialCombination } from '../enums/SpecialCombination';
|
|||
import type { ResizeEvent } from '../interfaces/ResizeEvent';
|
||||
import { ScreenScale } from '../enums/ScreenScale';
|
||||
import type { MousePosition } from '../interfaces/MousePosition';
|
||||
import type { SessionEvent, UserIronRdpErrorKind } from '../interfaces/session-event';
|
||||
import type { DesktopSize as IDesktopSize } from '../interfaces/DesktopSize';
|
||||
import type { SessionEvent, RemoteDesktopErrorKind, RemoteDesktopError } from '../interfaces/session-event';
|
||||
import type { DesktopSize } from '../interfaces/DesktopSize';
|
||||
import type { ClipboardTransaction } from '../interfaces/ClipboardTransaction';
|
||||
import type { ClipboardContent } from '../interfaces/ClipboardContent';
|
||||
import type { Session } from '../interfaces/Session';
|
||||
import type { DeviceEvent } from '../interfaces/DeviceEvent';
|
||||
import type { SessionTerminationInfo } from '../interfaces/SessionTerminationInfo';
|
||||
import type { RemoteDesktopModule } from '../interfaces/RemoteDesktopModule';
|
||||
|
||||
type OnRemoteClipboardChanged = (transaction: ClipboardTransaction) => void;
|
||||
type OnRemoteReceivedFormatsList = () => void;
|
||||
type OnForceClipboardUpdate = () => void;
|
||||
|
||||
export class WasmBridgeService {
|
||||
export class RemoteDesktopService {
|
||||
private module: RemoteDesktopModule;
|
||||
private _resize: Subject<ResizeEvent> = new Subject<ResizeEvent>();
|
||||
private mousePosition: BehaviorSubject<MousePosition> = new BehaviorSubject<MousePosition>({
|
||||
x: 0,
|
||||
|
@ -62,16 +58,29 @@ export class WasmBridgeService {
|
|||
height: number;
|
||||
}>();
|
||||
|
||||
constructor() {
|
||||
constructor(module: RemoteDesktopModule) {
|
||||
this.resize = this._resize.asObservable();
|
||||
this.module = module;
|
||||
loggingService.info('Web bridge initialized.');
|
||||
}
|
||||
|
||||
constructClipboardTransaction(): ClipboardTransaction {
|
||||
return this.module.ClipboardTransaction.construct();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async init(debug: LogType) {
|
||||
loggingService.info('Loading wasm file.');
|
||||
await init();
|
||||
await this.module.init();
|
||||
loggingService.info('Initializing IronRDP.');
|
||||
ironrdp_init(LogType[debug]);
|
||||
this.module.iron_init(LogType[debug]);
|
||||
}
|
||||
|
||||
// If set to false, the clipboard will not be enabled and the callbacks will not be registered to the Rust side
|
||||
|
@ -115,12 +124,14 @@ export class WasmBridgeService {
|
|||
if (preventDefault) {
|
||||
event.preventDefault(); // prevent default behavior (context menu, etc)
|
||||
}
|
||||
const mouseFnc = isDown ? DeviceEvent.new_mouse_button_pressed : DeviceEvent.new_mouse_button_released;
|
||||
const mouseFnc = isDown
|
||||
? this.module.DeviceEvent.mouse_button_pressed
|
||||
: this.module.DeviceEvent.mouse_button_released;
|
||||
this.doTransactionFromDeviceEvents([mouseFnc(event.button)]);
|
||||
}
|
||||
|
||||
updateMousePosition(position: MousePosition) {
|
||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_mouse_move(position.x, position.y)]);
|
||||
this.doTransactionFromDeviceEvents([this.module.DeviceEvent.mouse_move(position.x, position.y)]);
|
||||
this.mousePosition.next(position);
|
||||
}
|
||||
|
||||
|
@ -131,12 +142,13 @@ export class WasmBridgeService {
|
|||
proxyAddress: string,
|
||||
serverDomain: string,
|
||||
authToken: string,
|
||||
desktopSize?: IDesktopSize,
|
||||
desktopSize?: DesktopSize,
|
||||
preConnectionBlob?: string,
|
||||
kdc_proxy_url?: string,
|
||||
use_display_control = true,
|
||||
): Observable<NewSessionInfo> {
|
||||
const sessionBuilder = SessionBuilder.new();
|
||||
const sessionBuilder = this.module.SessionBuilder.construct();
|
||||
|
||||
sessionBuilder.proxy_address(proxyAddress);
|
||||
sessionBuilder.destination(destination);
|
||||
sessionBuilder.server_domain(serverDomain);
|
||||
|
@ -146,13 +158,13 @@ export class WasmBridgeService {
|
|||
sessionBuilder.render_canvas(this.canvas!);
|
||||
sessionBuilder.set_cursor_style_callback_context(this);
|
||||
sessionBuilder.set_cursor_style_callback(this.setCursorStyleCallback);
|
||||
sessionBuilder.kdc_proxy_url(kdc_proxy_url);
|
||||
if (use_display_control) {
|
||||
sessionBuilder.use_display_control();
|
||||
}
|
||||
sessionBuilder.extension({ DisplayControl: use_display_control });
|
||||
|
||||
if (preConnectionBlob != null) {
|
||||
sessionBuilder.pcb(preConnectionBlob);
|
||||
sessionBuilder.extension({ Pcb: preConnectionBlob });
|
||||
}
|
||||
if (kdc_proxy_url != null) {
|
||||
sessionBuilder.extension({ KdcProxyUrl: kdc_proxy_url });
|
||||
}
|
||||
if (this.onRemoteClipboardChanged != null && this.enableClipboard) {
|
||||
sessionBuilder.remote_clipboard_changed_callback(this.onRemoteClipboardChanged);
|
||||
|
@ -165,21 +177,22 @@ export class WasmBridgeService {
|
|||
}
|
||||
|
||||
if (desktopSize != null) {
|
||||
sessionBuilder.desktop_size(DesktopSize.new(desktopSize.width, desktopSize.height));
|
||||
sessionBuilder.desktop_size(this.module.DesktopSize.construct(desktopSize.width, desktopSize.height));
|
||||
}
|
||||
|
||||
// Type guard to filter out errors
|
||||
function isSession(result: IronRdpError | Session): result is Session {
|
||||
return result instanceof Session;
|
||||
function isSession(result: RemoteDesktopError | Session): result is Session {
|
||||
// Check whether function exists. To make it more robust we can check every method.
|
||||
return (<Session>result).run !== undefined;
|
||||
}
|
||||
|
||||
return from(sessionBuilder.connect()).pipe(
|
||||
catchError((err: IronRdpError) => {
|
||||
catchError((err: RemoteDesktopError) => {
|
||||
this.raiseSessionEvent({
|
||||
type: SessionEventType.ERROR,
|
||||
data: {
|
||||
backtrace: () => err.backtrace(),
|
||||
kind: () => err.kind() as number as UserIronRdpErrorKind,
|
||||
kind: () => err.kind() as number as RemoteDesktopErrorKind,
|
||||
},
|
||||
});
|
||||
return of(err);
|
||||
|
@ -188,7 +201,7 @@ export class WasmBridgeService {
|
|||
map((session: Session) => {
|
||||
from(session.run())
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
catchError((err: RemoteDesktopError) => {
|
||||
this.setVisibility(false);
|
||||
this.raiseSessionEvent({
|
||||
type: SessionEventType.ERROR,
|
||||
|
@ -245,7 +258,7 @@ export class WasmBridgeService {
|
|||
mouseWheel(event: WheelEvent) {
|
||||
const vertical = event.deltaY !== 0;
|
||||
const rotation = vertical ? event.deltaY : event.deltaX;
|
||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_wheel_rotations(vertical, -rotation)]);
|
||||
this.doTransactionFromDeviceEvents([this.module.DeviceEvent.wheel_rotations(vertical, -rotation)]);
|
||||
}
|
||||
|
||||
setVisibility(state: boolean) {
|
||||
|
@ -274,6 +287,13 @@ export class WasmBridgeService {
|
|||
return onClipboardChangedPromise();
|
||||
}
|
||||
|
||||
onClipboardChangedEmpty(): Promise<void> {
|
||||
const onClipboardChangedPromise = async () => {
|
||||
await this.session?.on_clipboard_paste(this.module.ClipboardTransaction.construct());
|
||||
};
|
||||
return onClipboardChangedPromise();
|
||||
}
|
||||
|
||||
setKeyboardUnicodeMode(use_unicode: boolean) {
|
||||
this.keyboardUnicodeMode = use_unicode;
|
||||
}
|
||||
|
@ -314,11 +334,11 @@ export class WasmBridgeService {
|
|||
let unicodeEvent;
|
||||
|
||||
if (evt.type === 'keydown') {
|
||||
keyEvent = DeviceEvent.new_key_pressed;
|
||||
unicodeEvent = DeviceEvent.new_unicode_pressed;
|
||||
keyEvent = this.module.DeviceEvent.key_pressed;
|
||||
unicodeEvent = this.module.DeviceEvent.unicode_pressed;
|
||||
} else if (evt.type === 'keyup') {
|
||||
keyEvent = DeviceEvent.new_key_released;
|
||||
unicodeEvent = DeviceEvent.new_unicode_released;
|
||||
keyEvent = this.module.DeviceEvent.key_released;
|
||||
unicodeEvent = this.module.DeviceEvent.unicode_released;
|
||||
}
|
||||
|
||||
let sendAsUnicode = true;
|
||||
|
@ -449,7 +469,7 @@ export class WasmBridgeService {
|
|||
}
|
||||
|
||||
private doTransactionFromDeviceEvents(deviceEvents: DeviceEvent[]) {
|
||||
const transaction = InputTransaction.new();
|
||||
const transaction = this.module.InputTransaction.construct();
|
||||
deviceEvents.forEach((event) => transaction.add_event(event));
|
||||
this.session?.apply_inputs(transaction);
|
||||
}
|
||||
|
@ -460,18 +480,21 @@ export class WasmBridgeService {
|
|||
const suppr = parseInt('0xE053', 16);
|
||||
|
||||
this.doTransactionFromDeviceEvents([
|
||||
DeviceEvent.new_key_pressed(ctrl),
|
||||
DeviceEvent.new_key_pressed(alt),
|
||||
DeviceEvent.new_key_pressed(suppr),
|
||||
DeviceEvent.new_key_released(ctrl),
|
||||
DeviceEvent.new_key_released(alt),
|
||||
DeviceEvent.new_key_released(suppr),
|
||||
this.module.DeviceEvent.key_pressed(ctrl),
|
||||
this.module.DeviceEvent.key_pressed(alt),
|
||||
this.module.DeviceEvent.key_pressed(suppr),
|
||||
this.module.DeviceEvent.key_released(ctrl),
|
||||
this.module.DeviceEvent.key_released(alt),
|
||||
this.module.DeviceEvent.key_released(suppr),
|
||||
]);
|
||||
}
|
||||
|
||||
private sendMeta() {
|
||||
const meta = parseInt('0xE05B', 16);
|
||||
|
||||
this.doTransactionFromDeviceEvents([DeviceEvent.new_key_pressed(meta), DeviceEvent.new_key_released(meta)]);
|
||||
this.doTransactionFromDeviceEvents([
|
||||
this.module.DeviceEvent.key_pressed(meta),
|
||||
this.module.DeviceEvent.key_released(meta),
|
||||
]);
|
||||
}
|
||||
}
|
8
web-client/iron-remote-desktop/tsconfig.node.json
Normal file
8
web-client/iron-remote-desktop/tsconfig.node.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -9,7 +9,7 @@ export default defineConfig({
|
|||
build: {
|
||||
lib: {
|
||||
entry: './src/main.ts',
|
||||
name: 'IronRemoteGui',
|
||||
name: 'IronRemoteDesktop',
|
||||
formats: ['es'],
|
||||
},
|
||||
},
|
|
@ -1,27 +0,0 @@
|
|||
import { spawn } from 'child_process';
|
||||
|
||||
let run = async function (command, cwd) {
|
||||
return new Promise((resolve) => {
|
||||
const buildCommand = spawn(command, {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
cwd: cwd,
|
||||
env: { ...process.env, RUSTFLAGS: '-Ctarget-feature=+simd128' },
|
||||
});
|
||||
|
||||
buildCommand.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
|
||||
buildCommand.stderr.on('data', (data) => {
|
||||
console.error(`${data}`);
|
||||
});
|
||||
|
||||
buildCommand.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
await run('wasm-pack build --target web', '../../crates/ironrdp-web');
|
|
@ -1,9 +0,0 @@
|
|||
export * as default from './iron-remote-gui.svelte';
|
||||
|
||||
export type { UserInteraction } from './interfaces/UserInteraction';
|
||||
export type { ResizeEvent } from './interfaces/ResizeEvent';
|
||||
export type { NewSessionInfo } from './interfaces/NewSessionInfo';
|
||||
export type { ServerRect } from './interfaces/ServerRect';
|
||||
export type { DesktopSize } from './interfaces/DesktopSize';
|
||||
export type { SessionEvent, UserIronRdpError, UserIronRdpErrorKind } from './interfaces/session-event';
|
||||
export type { SessionEventType } from './enums/SessionEventType';
|
3
web-client/iron-svelte-client/.gitignore
vendored
3
web-client/iron-svelte-client/.gitignore
vendored
|
@ -3,4 +3,5 @@
|
|||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/static/iron-remote-gui/
|
||||
/static/iron-remote-desktop-rdp/
|
||||
/static/iron-remote-desktop/
|
|
@ -1,7 +1,7 @@
|
|||
# SvelteKit UI for IronRDP
|
||||
|
||||
Web-based frontend using [`SvelteKit`](https://kit.svelte.dev/) and [`Material`](https://material.io) frameworks.
|
||||
This is a simple wrapper around the `iron-remote-gui` Web Component demonstrating how to use the API.
|
||||
This is a simple wrapper around the `iron-remote-desktop` Web Component demonstrating how to use the API.
|
||||
|
||||
Note that this demonstration client is not intended to be used in production as-is.
|
||||
Devolutions is shipping well-integrated, production-ready IronRDP web clients as part of:
|
||||
|
@ -111,13 +111,13 @@ If you have a Rust toolchain available, you can use the [`tokengen`][tokengen] t
|
|||
|
||||
## Run in development mode
|
||||
|
||||
First, run `npm install` in the [iron-remote-gui](../iron-remote-gui/) folder, and then `npm install` in [iron-svelte-client](./) folder.
|
||||
First, run `npm install` in [iron-remote-desktop](../iron-remote-desktop) and [iron-remote-desktop-rdp](../iron-remote-desktop-rdp) folders, and then `npm install` in [iron-svelte-client](./) folder.
|
||||
|
||||
You can then start the dev server with either:
|
||||
|
||||
- `npm run dev` - Runs only the final application.
|
||||
- `npm run dev-all` - Builds WASM module and `iron-remote-gui` prior to starting the dev server.
|
||||
- `npm run dev-no-wasm` - Only builds `iron-remote-gui` prior to starting the dev server.
|
||||
- `npm run dev-all` - Builds WASM module and `iron-remote-desktop` prior to starting the dev server.
|
||||
- `npm run dev-no-wasm` - Only builds `iron-remote-desktop` prior to starting the dev server.
|
||||
|
||||
You can build distribution files with `npm run build`.
|
||||
Files are to be found in `./iron-svelte-client/build/browser`.
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import { spawn } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { argv } from 'node:process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
let noWasm = false;
|
||||
|
||||
let assetIronRemoteGuiFolder = './static/iron-remote-gui';
|
||||
const assetIronRemoteDesktopFolder = './static/iron-remote-desktop';
|
||||
const assetIronRemoteDesktopRdpFolder = './static/iron-remote-desktop-rdp';
|
||||
|
||||
argv.forEach((val, index) => {
|
||||
if (index === 2 && val === 'no-wasm') {
|
||||
|
@ -17,8 +13,8 @@ argv.forEach((val, index) => {
|
|||
}
|
||||
});
|
||||
|
||||
let run = async function (command, cwd) {
|
||||
return new Promise((resolve) => {
|
||||
const run = async (command, cwd) => {
|
||||
try {
|
||||
const buildCommand = spawn(command, { stdio: 'pipe', shell: true, cwd: cwd });
|
||||
|
||||
buildCommand.stdout.on('data', (data) => {
|
||||
|
@ -29,29 +25,36 @@ let run = async function (command, cwd) {
|
|||
console.error(`${data}`);
|
||||
});
|
||||
|
||||
buildCommand.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
buildCommand.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Process exited with code ${code}`));
|
||||
} else {
|
||||
console.log(`Child process exited successfully with code ${code}`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Failed to execute the process: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
let copyCoreFiles = async function () {
|
||||
console.log('Copying core files…');
|
||||
await fs.remove(assetIronRemoteGuiFolder);
|
||||
return new Promise((resolve) => {
|
||||
let source = '../iron-remote-gui/dist';
|
||||
let destination = assetIronRemoteGuiFolder;
|
||||
const copyCoreFiles = async () => {
|
||||
try {
|
||||
console.log('Copying core files…');
|
||||
await fs.remove(assetIronRemoteDesktopFolder);
|
||||
await fs.remove(assetIronRemoteDesktopRdpFolder);
|
||||
|
||||
fs.copy(source, destination, function (err) {
|
||||
if (err) {
|
||||
console.log('An error occurred while copying core files.');
|
||||
return console.error(err);
|
||||
}
|
||||
console.log('Core files were copied successfully');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const source = '../iron-remote-desktop/dist';
|
||||
const sourceRdp = '../iron-remote-desktop-rdp/dist';
|
||||
|
||||
await fs.copy(source, assetIronRemoteDesktopFolder);
|
||||
await fs.copy(sourceRdp, assetIronRemoteDesktopRdpFolder);
|
||||
console.log('Core files were copied successfully');
|
||||
} catch (err) {
|
||||
console.error(`An error occurred while copying core files: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
let buildCommand = 'npm run build';
|
||||
|
@ -59,5 +62,6 @@ if (noWasm) {
|
|||
buildCommand = 'npm run build-alone';
|
||||
}
|
||||
|
||||
await run(buildCommand, '../iron-remote-gui');
|
||||
await run(buildCommand, '../iron-remote-desktop');
|
||||
await run(buildCommand, '../iron-remote-desktop-rdp');
|
||||
await copyCoreFiles();
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<link href="%sveltekit.assets%/beercss/beer.min.css" rel="stylesheet" />
|
||||
<link href="%sveltekit.assets%/theme.css" rel="stylesheet" />
|
||||
<script src="%sveltekit.assets%/beercss/beer.min.js" type="text/javascript"></script>
|
||||
<script type="module" src="%sveltekit.assets%/iron-remote-gui/iron-remote-gui.js"></script>
|
||||
<script type="module" src="%sveltekit.assets%/iron-remote-desktop/iron-remote-desktop.js"></script>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { currentSession, userInteractionService } from '../../services/session.service';
|
||||
import { catchError, filter } from 'rxjs/operators';
|
||||
import type { UserInteraction, NewSessionInfo } from '../../../static/iron-remote-gui';
|
||||
import type { UserInteraction, NewSessionInfo } from '../../../static/iron-remote-desktop';
|
||||
import { from, of } from 'rxjs';
|
||||
import { toast } from '$lib/messages/message-store';
|
||||
import { showLogin } from '$lib/login/login-store';
|
||||
import type { DesktopSize } from '../../models/desktop-size';
|
||||
import { DesktopSize } from '../../models/desktop-size';
|
||||
|
||||
let username = 'Administrator';
|
||||
let password = 'DevoLabs123!';
|
||||
|
@ -14,10 +14,7 @@
|
|||
let domain = '';
|
||||
let authtoken = '';
|
||||
let kdc_proxy_url = '';
|
||||
let desktopSize: DesktopSize = {
|
||||
width: 1280,
|
||||
height: 768,
|
||||
};
|
||||
let desktopSize = new DesktopSize(1280, 768);
|
||||
let pcb: string;
|
||||
let pop_up = false;
|
||||
let enable_clipboard = true;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
||||
import type { UserInteraction } from '../../../static/iron-remote-gui';
|
||||
import type { UserInteraction } from '../../../static/iron-remote-desktop';
|
||||
import IronRdp from '../../../static/iron-remote-desktop-rdp';
|
||||
|
||||
let uiService: UserInteraction;
|
||||
let cursorOverrideActive = false;
|
||||
|
@ -93,10 +94,10 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
const el = document.querySelector('iron-remote-gui');
|
||||
const el = document.querySelector('iron-remote-desktop');
|
||||
|
||||
if (el == null) {
|
||||
throw '`iron-remote-gui` element not found';
|
||||
throw '`iron-remote-desktop` element not found';
|
||||
}
|
||||
|
||||
el.addEventListener('ready', (e) => {
|
||||
|
@ -110,11 +111,7 @@
|
|||
id="popup-screen"
|
||||
style="display: flex; height: 100%; flex-direction: column; background-color: #2e2e2e; position: relative"
|
||||
on:mousemove={(event) => {
|
||||
if (event.clientY < 100) {
|
||||
showUtilityBar = true;
|
||||
} else {
|
||||
showUtilityBar = false;
|
||||
}
|
||||
showUtilityBar = event.clientY < 100;
|
||||
}}
|
||||
>
|
||||
<div class="tool-bar" class:hidden={!showUtilityBar}>
|
||||
|
@ -139,7 +136,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<iron-remote-gui debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" />
|
||||
<iron-remote-desktop debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" module={IronRdp} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { setCurrentSessionActive, userInteractionService } from '../../services/session.service';
|
||||
import { showLogin } from '$lib/login/login-store';
|
||||
import type { UserInteraction } from '../../../static/iron-remote-gui';
|
||||
import type { UserInteraction } from '../../../static/iron-remote-desktop';
|
||||
import IronRdp from '../../../static/iron-remote-desktop-rdp';
|
||||
|
||||
let uiService: UserInteraction;
|
||||
let cursorOverrideActive = false;
|
||||
|
@ -47,10 +48,10 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
let el = document.querySelector('iron-remote-gui');
|
||||
let el = document.querySelector('iron-remote-desktop');
|
||||
|
||||
if (el == null) {
|
||||
throw '`iron-remote-gui` element not found';
|
||||
throw '`iron-remote-desktop` element not found';
|
||||
}
|
||||
|
||||
el.addEventListener('ready', (e) => {
|
||||
|
@ -100,7 +101,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<iron-remote-gui debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" />
|
||||
<iron-remote-desktop debugwasm="INFO" verbose="true" scale="fit" flexcenter="true" module={IronRdp} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
export class DesktopSize {
|
||||
width!: number;
|
||||
height!: number;
|
||||
import type { DesktopSize as IDesktopSize } from './../../../iron-remote-desktop/src/interfaces/DesktopSize';
|
||||
export class DesktopSize implements IDesktopSize {
|
||||
constructor(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
construct(width: number, height: number): DesktopSize {
|
||||
return new DesktopSize(width, height);
|
||||
}
|
||||
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export class Rect {
|
||||
top!: number;
|
||||
left!: number;
|
||||
width!: number;
|
||||
height!: number;
|
||||
buffer!: ArrayBuffer;
|
||||
}
|
|
@ -2,7 +2,7 @@ import type { Guid } from 'guid-typescript';
|
|||
import type { Writable } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
import { Session } from '../models/session';
|
||||
import type { UserInteraction } from '../../static/iron-remote-gui';
|
||||
import type { UserInteraction } from '../../static/iron-remote-desktop';
|
||||
|
||||
export const userInteractionService: Writable<UserInteraction> = writable();
|
||||
export const currentSession: Writable<Session> = writable();
|
||||
|
|
|
@ -6,6 +6,11 @@ import topLevelAwait from 'vite-plugin-top-level-await';
|
|||
const config: UserConfig = {
|
||||
mode: 'process.env.MODE' || 'development',
|
||||
plugins: [sveltekit(), wasm(), topLevelAwait()],
|
||||
server: {
|
||||
fs: {
|
||||
allow: ['./static'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -76,7 +76,8 @@ pub fn lock_files(sh: &Shell) -> anyhow::Result<()> {
|
|||
const LOCK_FILES: &[&str] = &[
|
||||
"Cargo.lock",
|
||||
"fuzz/Cargo.lock",
|
||||
"web-client/iron-remote-gui/package-lock.json",
|
||||
"web-client/iron-remote-desktop/package-lock.json",
|
||||
"web-client/iron-remote-desktop-rdp/package-lock.json",
|
||||
"web-client/iron-svelte-client/package-lock.json",
|
||||
];
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ pub fn workspace(sh: &Shell) -> anyhow::Result<()> {
|
|||
println!("Done.");
|
||||
|
||||
println!("Remove npm folders…");
|
||||
sh.remove_path("./web-client/iron-remote-gui/node_modules")?;
|
||||
sh.remove_path("./web-client/iron-remote-gui/dist")?;
|
||||
sh.remove_path("./web-client/iron-remote-desktop/node_modules")?;
|
||||
sh.remove_path("./web-client/iron-remote-desktop/dist")?;
|
||||
sh.remove_path("./web-client/iron-remote-desktop-rdp/node_modules")?;
|
||||
sh.remove_path("./web-client/iron-remote-desktop-rdp/dist")?;
|
||||
sh.remove_path("./web-client/iron-svelte-client/node_modules")?;
|
||||
println!("Done.");
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ TASKS:
|
|||
wasm install Install dependencies required to build the WASM target
|
||||
web check Ensure Web Client is building without error
|
||||
web install Install dependencies required to build and run Web Client
|
||||
web build Build the Web Client
|
||||
web run Run SvelteKit-based standalone Web Client
|
||||
ffi install Install all requirements for ffi tasks
|
||||
ffi build [--release] Build DLL for FFI (default is debug)
|
||||
|
@ -88,6 +89,7 @@ pub enum Action {
|
|||
WasmInstall,
|
||||
WebCheck,
|
||||
WebInstall,
|
||||
WebBuild,
|
||||
WebRun,
|
||||
FfiInstall,
|
||||
FfiBuildDll {
|
||||
|
@ -159,6 +161,7 @@ pub fn parse_args() -> anyhow::Result<Args> {
|
|||
Some("web") => match args.subcommand()?.as_deref() {
|
||||
Some("check") => Action::WebCheck,
|
||||
Some("install") => Action::WebInstall,
|
||||
Some("build") => Action::WebBuild,
|
||||
Some("run") => Action::WebRun,
|
||||
Some(unknown) => anyhow::bail!("unknown web action: {unknown}"),
|
||||
None => Action::ShowHelp,
|
||||
|
|
|
@ -110,6 +110,7 @@ fn main() -> anyhow::Result<()> {
|
|||
Action::WasmCheck => wasm::check(&sh)?,
|
||||
Action::WasmInstall => wasm::install(&sh)?,
|
||||
Action::WebCheck => web::check(&sh)?,
|
||||
Action::WebBuild => web::build(&sh, false)?,
|
||||
Action::WebInstall => web::install(&sh)?,
|
||||
Action::WebRun => web::run(&sh)?,
|
||||
Action::FfiInstall => ffi::install(&sh)?,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use crate::prelude::*;
|
||||
use std::fs;
|
||||
|
||||
const IRON_REMOTE_GUI_PATH: &str = "./web-client/iron-remote-gui";
|
||||
const IRON_REMOTE_DESKTOP_PATH: &str = "./web-client/iron-remote-desktop";
|
||||
const IRON_REMOTE_DESKTOP_RDP_PATH: &str = "./web-client/iron-remote-desktop-rdp";
|
||||
const IRON_SVELTE_CLIENT_PATH: &str = "./web-client/iron-svelte-client";
|
||||
const IRONRDP_WEB_PATH: &str = "./crates/ironrdp-web";
|
||||
const IRONRDP_WEB_PACKAGE_JS_PATH: &str = "./crates/ironrdp-web/pkg/ironrdp_web.js";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const NPM: &str = "npm";
|
||||
|
@ -12,7 +15,8 @@ const NPM: &str = "npm.cmd";
|
|||
pub fn install(sh: &Shell) -> anyhow::Result<()> {
|
||||
let _s = Section::new("WEB-INSTALL");
|
||||
|
||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} install")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} install")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} install")?;
|
||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} install")?;
|
||||
|
||||
cargo_install(sh, &WASM_PACK)?;
|
||||
|
@ -25,8 +29,10 @@ pub fn check(sh: &Shell) -> anyhow::Result<()> {
|
|||
|
||||
build(sh, true)?;
|
||||
|
||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run check")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run lint")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} run check")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_PATH, "{NPM} run lint")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} run check")?;
|
||||
run_cmd_in!(sh, IRON_REMOTE_DESKTOP_RDP_PATH, "{NPM} run lint")?;
|
||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run check")?;
|
||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run lint")?;
|
||||
|
||||
|
@ -36,22 +42,34 @@ pub fn check(sh: &Shell) -> anyhow::Result<()> {
|
|||
pub fn run(sh: &Shell) -> anyhow::Result<()> {
|
||||
let _s = Section::new("WEB-RUN");
|
||||
|
||||
build(sh, false)?;
|
||||
|
||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run dev-no-wasm")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(sh: &Shell, wasm_pack_dev: bool) -> anyhow::Result<()> {
|
||||
pub fn build(sh: &Shell, wasm_pack_dev: bool) -> anyhow::Result<()> {
|
||||
if wasm_pack_dev {
|
||||
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --dev --target web")?;
|
||||
} else {
|
||||
let _env_guard = sh.push_env("RUSTFLAGS", "-Ctarget-feature=+simd128");
|
||||
let _env_guard = sh.push_env("RUSTFLAGS", "-Ctarget-feature=+simd128,+bulk-memory");
|
||||
run_cmd_in!(sh, IRONRDP_WEB_PATH, "wasm-pack build --target web")?;
|
||||
}
|
||||
|
||||
run_cmd_in!(sh, IRON_REMOTE_GUI_PATH, "{NPM} run build-alone")?;
|
||||
let ironrdp_web_js_file_path = sh.current_dir().join(IRONRDP_WEB_PACKAGE_JS_PATH);
|
||||
|
||||
let ironrdp_web_js_content = fs::read_to_string(&ironrdp_web_js_file_path)?;
|
||||
|
||||
// Modify the js file to get rid of the `URL` object.
|
||||
// Vite doesn't work properly with inlined urls in `new URL(url, import.meta.url)`.
|
||||
let ironrdp_web_js_content = format!(
|
||||
"import wasmUrl from './ironrdp_web_bg.wasm?url';\n\n{}",
|
||||
ironrdp_web_js_content
|
||||
);
|
||||
let ironrdp_web_js_content =
|
||||
ironrdp_web_js_content.replace("new URL('ironrdp_web_bg.wasm', import.meta.url)", "wasmUrl");
|
||||
|
||||
fs::write(&ironrdp_web_js_file_path, ironrdp_web_js_content)?;
|
||||
|
||||
run_cmd_in!(sh, IRON_SVELTE_CLIENT_PATH, "{NPM} run build-no-wasm")?;
|
||||
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue