iron_remote_desktop/
lib.rs

1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3
4mod clipboard;
5mod cursor;
6mod desktop_size;
7mod error;
8mod extension;
9mod input;
10mod session;
11
12pub use clipboard::{ClipboardData, ClipboardItem};
13pub use cursor::CursorStyle;
14pub use desktop_size::DesktopSize;
15pub use error::{IronError, IronErrorKind};
16pub use extension::Extension;
17pub use input::{DeviceEvent, InputTransaction, RotationUnit};
18pub use session::{Session, SessionBuilder, SessionTerminationInfo};
19
20pub trait RemoteDesktopApi {
21    type Session: Session;
22    type SessionBuilder: SessionBuilder;
23    type SessionTerminationInfo: SessionTerminationInfo;
24    type DeviceEvent: DeviceEvent;
25    type InputTransaction: InputTransaction;
26    type ClipboardData: ClipboardData;
27    type ClipboardItem: ClipboardItem;
28    type Error: IronError;
29
30    /// Called before the logger is set.
31    fn pre_setup() {}
32
33    /// Called after the logger is set.
34    fn post_setup() {}
35}
36
37#[macro_export]
38macro_rules! make_bridge {
39    ($api:ty) => {
40        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
41        pub struct Session(<$api as $crate::RemoteDesktopApi>::Session);
42
43        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
44        pub struct SessionBuilder(<$api as $crate::RemoteDesktopApi>::SessionBuilder);
45
46        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
47        pub struct SessionTerminationInfo(<$api as $crate::RemoteDesktopApi>::SessionTerminationInfo);
48
49        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
50        pub struct DeviceEvent(<$api as $crate::RemoteDesktopApi>::DeviceEvent);
51
52        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
53        pub struct InputTransaction(<$api as $crate::RemoteDesktopApi>::InputTransaction);
54
55        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
56        pub struct ClipboardData(<$api as $crate::RemoteDesktopApi>::ClipboardData);
57
58        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
59        pub struct ClipboardItem(<$api as $crate::RemoteDesktopApi>::ClipboardItem);
60
61        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
62        pub struct IronError(<$api as $crate::RemoteDesktopApi>::Error);
63
64        impl From<<$api as $crate::RemoteDesktopApi>::Session> for Session {
65            fn from(value: <$api as $crate::RemoteDesktopApi>::Session) -> Self {
66                Self(value)
67            }
68        }
69
70        impl From<<$api as $crate::RemoteDesktopApi>::SessionBuilder> for SessionBuilder {
71            fn from(value: <$api as $crate::RemoteDesktopApi>::SessionBuilder) -> Self {
72                Self(value)
73            }
74        }
75
76        impl From<<$api as $crate::RemoteDesktopApi>::SessionTerminationInfo> for SessionTerminationInfo {
77            fn from(value: <$api as $crate::RemoteDesktopApi>::SessionTerminationInfo) -> Self {
78                Self(value)
79            }
80        }
81
82        impl From<<$api as $crate::RemoteDesktopApi>::DeviceEvent> for DeviceEvent {
83            fn from(value: <$api as $crate::RemoteDesktopApi>::DeviceEvent) -> Self {
84                Self(value)
85            }
86        }
87
88        impl From<<$api as $crate::RemoteDesktopApi>::InputTransaction> for InputTransaction {
89            fn from(value: <$api as $crate::RemoteDesktopApi>::InputTransaction) -> Self {
90                Self(value)
91            }
92        }
93
94        impl From<<$api as $crate::RemoteDesktopApi>::ClipboardData> for ClipboardData {
95            fn from(value: <$api as $crate::RemoteDesktopApi>::ClipboardData) -> Self {
96                Self(value)
97            }
98        }
99
100        impl From<<$api as $crate::RemoteDesktopApi>::ClipboardItem> for ClipboardItem {
101            fn from(value: <$api as $crate::RemoteDesktopApi>::ClipboardItem) -> Self {
102                Self(value)
103            }
104        }
105
106        impl From<<$api as $crate::RemoteDesktopApi>::Error> for IronError {
107            fn from(value: <$api as $crate::RemoteDesktopApi>::Error) -> Self {
108                Self(value)
109            }
110        }
111
112        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
113        #[doc(hidden)]
114        pub fn setup(log_level: &str) {
115            <$api as $crate::RemoteDesktopApi>::pre_setup();
116            $crate::internal::setup(log_level);
117            <$api as $crate::RemoteDesktopApi>::post_setup();
118        }
119
120        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
121        #[doc(hidden)]
122        impl Session {
123            pub async fn run(&self) -> Result<SessionTerminationInfo, IronError> {
124                $crate::Session::run(&self.0)
125                    .await
126                    .map(SessionTerminationInfo)
127                    .map_err(IronError)
128            }
129
130            #[wasm_bindgen(js_name = desktopSize)]
131            pub fn desktop_size(&self) -> $crate::DesktopSize {
132                $crate::Session::desktop_size(&self.0)
133            }
134
135            #[wasm_bindgen(js_name = applyInputs)]
136            pub fn apply_inputs(&self, transaction: InputTransaction) -> Result<(), IronError> {
137                $crate::Session::apply_inputs(&self.0, transaction.0).map_err(IronError)
138            }
139
140            #[wasm_bindgen(js_name = releaseAllInputs)]
141            pub fn release_all_inputs(&self) -> Result<(), IronError> {
142                $crate::Session::release_all_inputs(&self.0).map_err(IronError)
143            }
144
145            #[wasm_bindgen(js_name = synchronizeLockKeys)]
146            pub fn synchronize_lock_keys(
147                &self,
148                scroll_lock: bool,
149                num_lock: bool,
150                caps_lock: bool,
151                kana_lock: bool,
152            ) -> Result<(), IronError> {
153                $crate::Session::synchronize_lock_keys(&self.0, scroll_lock, num_lock, caps_lock, kana_lock)
154                    .map_err(IronError)
155            }
156
157            pub fn shutdown(&self) -> Result<(), IronError> {
158                $crate::Session::shutdown(&self.0).map_err(IronError)
159            }
160
161            #[wasm_bindgen(js_name = onClipboardPaste)]
162            pub async fn on_clipboard_paste(&self, content: &ClipboardData) -> Result<(), IronError> {
163                $crate::Session::on_clipboard_paste(&self.0, &content.0)
164                    .await
165                    .map_err(IronError)
166            }
167
168            pub fn resize(
169                &self,
170                width: u32,
171                height: u32,
172                scale_factor: Option<u32>,
173                physical_width: Option<u32>,
174                physical_height: Option<u32>,
175            ) {
176                $crate::Session::resize(
177                    &self.0,
178                    width,
179                    height,
180                    scale_factor,
181                    physical_width,
182                    physical_height,
183                );
184            }
185
186            #[wasm_bindgen(js_name = supportsUnicodeKeyboardShortcuts)]
187            pub fn supports_unicode_keyboard_shortcuts(&self) -> bool {
188                $crate::Session::supports_unicode_keyboard_shortcuts(&self.0)
189            }
190
191            #[wasm_bindgen(js_name = invokeExtension)]
192            pub fn invoke_extension(
193                &self,
194                ext: $crate::Extension,
195            ) -> Result<$crate::internal::wasm_bindgen::JsValue, IronError> {
196                <<$api as $crate::RemoteDesktopApi>::Session as $crate::Session>::invoke_extension(&self.0, ext)
197                    .map_err(IronError)
198            }
199        }
200
201        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
202        #[doc(hidden)]
203        impl SessionBuilder {
204            #[wasm_bindgen(constructor)]
205            pub fn create() -> Self {
206                Self(<<$api as $crate::RemoteDesktopApi>::SessionBuilder as $crate::SessionBuilder>::create())
207            }
208
209            pub fn username(&self, username: String) -> Self {
210                Self($crate::SessionBuilder::username(&self.0, username))
211            }
212
213            pub fn destination(&self, destination: String) -> Self {
214                Self($crate::SessionBuilder::destination(&self.0, destination))
215            }
216
217            #[wasm_bindgen(js_name = serverDomain)]
218            pub fn server_domain(&self, server_domain: String) -> Self {
219                Self($crate::SessionBuilder::server_domain(&self.0, server_domain))
220            }
221
222            pub fn password(&self, password: String) -> Self {
223                Self($crate::SessionBuilder::password(&self.0, password))
224            }
225
226            #[wasm_bindgen(js_name = proxyAddress)]
227            pub fn proxy_address(&self, address: String) -> Self {
228                Self($crate::SessionBuilder::proxy_address(&self.0, address))
229            }
230
231            #[wasm_bindgen(js_name = authToken)]
232            pub fn auth_token(&self, token: String) -> Self {
233                Self($crate::SessionBuilder::auth_token(&self.0, token))
234            }
235
236            #[wasm_bindgen(js_name = desktopSize)]
237            pub fn desktop_size(&self, desktop_size: $crate::DesktopSize) -> Self {
238                Self($crate::SessionBuilder::desktop_size(&self.0, desktop_size))
239            }
240
241            #[wasm_bindgen(js_name = renderCanvas)]
242            pub fn render_canvas(&self, canvas: $crate::internal::web_sys::HtmlCanvasElement) -> Self {
243                Self($crate::SessionBuilder::render_canvas(&self.0, canvas))
244            }
245
246            #[wasm_bindgen(js_name = setCursorStyleCallback)]
247            pub fn set_cursor_style_callback(&self, callback: $crate::internal::web_sys::js_sys::Function) -> Self {
248                Self($crate::SessionBuilder::set_cursor_style_callback(
249                    &self.0, callback,
250                ))
251            }
252
253            #[wasm_bindgen(js_name = setCursorStyleCallbackContext)]
254            pub fn set_cursor_style_callback_context(&self, context: $crate::internal::wasm_bindgen::JsValue) -> Self {
255                Self($crate::SessionBuilder::set_cursor_style_callback_context(
256                    &self.0, context,
257                ))
258            }
259
260            #[wasm_bindgen(js_name = remoteClipboardChangedCallback)]
261            pub fn remote_clipboard_changed_callback(
262                &self,
263                callback: $crate::internal::web_sys::js_sys::Function,
264            ) -> Self {
265                Self($crate::SessionBuilder::remote_clipboard_changed_callback(
266                    &self.0, callback,
267                ))
268            }
269
270            #[wasm_bindgen(js_name = forceClipboardUpdateCallback)]
271            pub fn force_clipboard_update_callback(
272                &self,
273                callback: $crate::internal::web_sys::js_sys::Function,
274            ) -> Self {
275                Self($crate::SessionBuilder::force_clipboard_update_callback(
276                    &self.0, callback,
277                ))
278            }
279
280            #[wasm_bindgen(js_name = canvasResizedCallback)]
281            pub fn canvas_resized_callback(&self, callback: $crate::internal::web_sys::js_sys::Function) -> Self {
282                Self($crate::SessionBuilder::canvas_resized_callback(&self.0, callback))
283            }
284
285            pub fn extension(&self, ext: $crate::Extension) -> Self {
286                Self($crate::SessionBuilder::extension(&self.0, ext))
287            }
288
289            pub async fn connect(&self) -> Result<Session, IronError> {
290                $crate::SessionBuilder::connect(&self.0)
291                    .await
292                    .map(Session)
293                    .map_err(IronError)
294            }
295        }
296
297        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
298        #[doc(hidden)]
299        impl SessionTerminationInfo {
300            pub fn reason(&self) -> String {
301                $crate::SessionTerminationInfo::reason(&self.0)
302            }
303        }
304
305        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
306        #[doc(hidden)]
307        impl DeviceEvent {
308            #[wasm_bindgen(js_name = mouseButtonPressed)]
309            pub fn mouse_button_pressed(button: u8) -> Self {
310                Self(
311                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_pressed(
312                        button,
313                    ),
314                )
315            }
316
317            #[wasm_bindgen(js_name = mouseButtonReleased)]
318            pub fn mouse_button_released(button: u8) -> Self {
319                Self(
320                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_released(
321                        button,
322                    ),
323                )
324            }
325
326            #[wasm_bindgen(js_name = mouseMove)]
327            pub fn mouse_move(x: u16, y: u16) -> Self {
328                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_move(x, y))
329            }
330
331            #[wasm_bindgen(js_name = wheelRotations)]
332            pub fn wheel_rotations(vertical: bool, rotation_amount: i16, rotation_unit: $crate::RotationUnit) -> Self {
333                Self(
334                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::wheel_rotations(
335                        vertical,
336                        rotation_amount,
337                        rotation_unit,
338                    ),
339                )
340            }
341
342            #[wasm_bindgen(js_name = keyPressed)]
343            pub fn key_pressed(scancode: u16) -> Self {
344                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_pressed(scancode))
345            }
346
347            #[wasm_bindgen(js_name = keyReleased)]
348            pub fn key_released(scancode: u16) -> Self {
349                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_released(scancode))
350            }
351
352            #[wasm_bindgen(js_name = unicodePressed)]
353            pub fn unicode_pressed(unicode: char) -> Self {
354                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_pressed(unicode))
355            }
356
357            #[wasm_bindgen(js_name = unicodeReleased)]
358            pub fn unicode_released(unicode: char) -> Self {
359                Self(
360                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_released(unicode),
361                )
362            }
363        }
364
365        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
366        #[doc(hidden)]
367        impl InputTransaction {
368            #[wasm_bindgen(constructor)]
369            pub fn create() -> Self {
370                Self(<<$api as $crate::RemoteDesktopApi>::InputTransaction as $crate::InputTransaction>::create())
371            }
372
373            #[wasm_bindgen(js_name = addEvent)]
374            pub fn add_event(&mut self, event: DeviceEvent) {
375                $crate::InputTransaction::add_event(&mut self.0, event.0);
376            }
377        }
378
379        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
380        #[doc(hidden)]
381        impl ClipboardData {
382            #[wasm_bindgen(constructor)]
383            pub fn create() -> Self {
384                Self(<<$api as $crate::RemoteDesktopApi>::ClipboardData as $crate::ClipboardData>::create())
385            }
386
387            #[wasm_bindgen(js_name = addText)]
388            pub fn add_text(&mut self, mime_type: &str, text: &str) {
389                $crate::ClipboardData::add_text(&mut self.0, mime_type, text);
390            }
391
392            #[wasm_bindgen(js_name = addBinary)]
393            pub fn add_binary(&mut self, mime_type: &str, binary: &[u8]) {
394                $crate::ClipboardData::add_binary(&mut self.0, mime_type, binary);
395            }
396
397            pub fn items(&self) -> Vec<ClipboardItem> {
398                $crate::ClipboardData::items(&self.0)
399                    .into_iter()
400                    .cloned()
401                    .map(ClipboardItem)
402                    .collect()
403            }
404
405            #[wasm_bindgen(js_name = isEmpty)]
406            pub fn is_empty(&self) -> bool {
407                $crate::ClipboardData::is_empty(&self.0)
408            }
409        }
410
411        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
412        #[doc(hidden)]
413        impl ClipboardItem {
414            #[wasm_bindgen(js_name = mimeType)]
415            pub fn mime_type(&self) -> String {
416                $crate::ClipboardItem::mime_type(&self.0).to_owned()
417            }
418
419            pub fn value(&self) -> $crate::internal::wasm_bindgen::JsValue {
420                $crate::ClipboardItem::value(&self.0).into()
421            }
422        }
423
424        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
425        #[doc(hidden)]
426        impl IronError {
427            pub fn backtrace(&self) -> String {
428                $crate::IronError::backtrace(&self.0)
429            }
430
431            pub fn kind(&self) -> $crate::IronErrorKind {
432                $crate::IronError::kind(&self.0)
433            }
434        }
435    };
436}
437
438#[doc(hidden)]
439pub mod internal {
440    #[doc(hidden)]
441    pub use wasm_bindgen;
442    #[doc(hidden)]
443    pub use web_sys;
444
445    #[doc(hidden)]
446    pub fn setup(log_level: &str) {
447        // When the `console_error_panic_hook` feature is enabled, we can call the
448        // `set_panic_hook` function at least once during initialization, and then
449        // we will get better error messages if our code ever panics.
450        //
451        // For more details see
452        // https://github.com/rustwasm/console_error_panic_hook#readme
453        #[cfg(feature = "panic_hook")]
454        console_error_panic_hook::set_once();
455
456        if let Ok(level) = log_level.parse::<tracing::Level>() {
457            set_logger_once(level);
458        }
459    }
460
461    fn set_logger_once(level: tracing::Level) {
462        use tracing_subscriber::filter::LevelFilter;
463        use tracing_subscriber::fmt::time::UtcTime;
464        use tracing_subscriber::prelude::*;
465        use tracing_web::MakeConsoleWriter;
466
467        static INIT: std::sync::Once = std::sync::Once::new();
468
469        INIT.call_once(|| {
470            let fmt_layer = tracing_subscriber::fmt::layer()
471                .with_ansi(false)
472                .with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
473                .with_writer(MakeConsoleWriter);
474
475            let level_filter = LevelFilter::from_level(level);
476
477            tracing_subscriber::registry().with(fmt_layer).with(level_filter).init();
478        })
479    }
480}