iron_remote_desktop/
lib.rs

1#![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};
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 = extensionCall)]
192            pub fn extension_call(
193                ext: $crate::Extension,
194            ) -> Result<$crate::internal::wasm_bindgen::JsValue, IronError> {
195                <<$api as $crate::RemoteDesktopApi>::Session as $crate::Session>::extension_call(ext).map_err(IronError)
196            }
197        }
198
199        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
200        #[doc(hidden)]
201        impl SessionBuilder {
202            #[wasm_bindgen(constructor)]
203            pub fn create() -> Self {
204                Self(<<$api as $crate::RemoteDesktopApi>::SessionBuilder as $crate::SessionBuilder>::create())
205            }
206
207            pub fn username(&self, username: String) -> Self {
208                Self($crate::SessionBuilder::username(&self.0, username))
209            }
210
211            pub fn destination(&self, destination: String) -> Self {
212                Self($crate::SessionBuilder::destination(&self.0, destination))
213            }
214
215            #[wasm_bindgen(js_name = serverDomain)]
216            pub fn server_domain(&self, server_domain: String) -> Self {
217                Self($crate::SessionBuilder::server_domain(&self.0, server_domain))
218            }
219
220            pub fn password(&self, password: String) -> Self {
221                Self($crate::SessionBuilder::password(&self.0, password))
222            }
223
224            #[wasm_bindgen(js_name = proxyAddress)]
225            pub fn proxy_address(&self, address: String) -> Self {
226                Self($crate::SessionBuilder::proxy_address(&self.0, address))
227            }
228
229            #[wasm_bindgen(js_name = authToken)]
230            pub fn auth_token(&self, token: String) -> Self {
231                Self($crate::SessionBuilder::auth_token(&self.0, token))
232            }
233
234            #[wasm_bindgen(js_name = desktopSize)]
235            pub fn desktop_size(&self, desktop_size: $crate::DesktopSize) -> Self {
236                Self($crate::SessionBuilder::desktop_size(&self.0, desktop_size))
237            }
238
239            #[wasm_bindgen(js_name = renderCanvas)]
240            pub fn render_canvas(&self, canvas: $crate::internal::web_sys::HtmlCanvasElement) -> Self {
241                Self($crate::SessionBuilder::render_canvas(&self.0, canvas))
242            }
243
244            #[wasm_bindgen(js_name = setCursorStyleCallback)]
245            pub fn set_cursor_style_callback(&self, callback: $crate::internal::web_sys::js_sys::Function) -> Self {
246                Self($crate::SessionBuilder::set_cursor_style_callback(
247                    &self.0, callback,
248                ))
249            }
250
251            #[wasm_bindgen(js_name = setCursorStyleCallbackContext)]
252            pub fn set_cursor_style_callback_context(&self, context: $crate::internal::wasm_bindgen::JsValue) -> Self {
253                Self($crate::SessionBuilder::set_cursor_style_callback_context(
254                    &self.0, context,
255                ))
256            }
257
258            #[wasm_bindgen(js_name = remoteClipboardChangedCallback)]
259            pub fn remote_clipboard_changed_callback(
260                &self,
261                callback: $crate::internal::web_sys::js_sys::Function,
262            ) -> Self {
263                Self($crate::SessionBuilder::remote_clipboard_changed_callback(
264                    &self.0, callback,
265                ))
266            }
267
268            #[wasm_bindgen(js_name = remoteReceivedFormatListCallback)]
269            pub fn remote_received_format_list_callback(
270                &self,
271                callback: $crate::internal::web_sys::js_sys::Function,
272            ) -> Self {
273                Self($crate::SessionBuilder::remote_received_format_list_callback(
274                    &self.0, callback,
275                ))
276            }
277
278            #[wasm_bindgen(js_name = forceClipboardUpdateCallback)]
279            pub fn force_clipboard_update_callback(
280                &self,
281                callback: $crate::internal::web_sys::js_sys::Function,
282            ) -> Self {
283                Self($crate::SessionBuilder::force_clipboard_update_callback(
284                    &self.0, callback,
285                ))
286            }
287
288            pub fn extension(&self, ext: $crate::Extension) -> Self {
289                Self($crate::SessionBuilder::extension(&self.0, ext))
290            }
291
292            pub async fn connect(&self) -> Result<Session, IronError> {
293                $crate::SessionBuilder::connect(&self.0)
294                    .await
295                    .map(Session)
296                    .map_err(IronError)
297            }
298        }
299
300        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
301        #[doc(hidden)]
302        impl SessionTerminationInfo {
303            pub fn reason(&self) -> String {
304                $crate::SessionTerminationInfo::reason(&self.0)
305            }
306        }
307
308        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
309        #[doc(hidden)]
310        impl DeviceEvent {
311            #[wasm_bindgen(js_name = mouseButtonPressed)]
312            pub fn mouse_button_pressed(button: u8) -> Self {
313                Self(
314                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_pressed(
315                        button,
316                    ),
317                )
318            }
319
320            #[wasm_bindgen(js_name = mouseButtonReleased)]
321            pub fn mouse_button_released(button: u8) -> Self {
322                Self(
323                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_button_released(
324                        button,
325                    ),
326                )
327            }
328
329            #[wasm_bindgen(js_name = mouseMove)]
330            pub fn mouse_move(x: u16, y: u16) -> Self {
331                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::mouse_move(x, y))
332            }
333
334            #[wasm_bindgen(js_name = wheelRotations)]
335            pub fn wheel_rotations(vertical: bool, rotation_units: i16) -> Self {
336                Self(
337                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::wheel_rotations(
338                        vertical,
339                        rotation_units,
340                    ),
341                )
342            }
343
344            #[wasm_bindgen(js_name = keyPressed)]
345            pub fn key_pressed(scancode: u16) -> Self {
346                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_pressed(scancode))
347            }
348
349            #[wasm_bindgen(js_name = keyReleased)]
350            pub fn key_released(scancode: u16) -> Self {
351                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::key_released(scancode))
352            }
353
354            #[wasm_bindgen(js_name = unicodePressed)]
355            pub fn unicode_pressed(unicode: char) -> Self {
356                Self(<<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_pressed(unicode))
357            }
358
359            #[wasm_bindgen(js_name = unicodeReleased)]
360            pub fn unicode_released(unicode: char) -> Self {
361                Self(
362                    <<$api as $crate::RemoteDesktopApi>::DeviceEvent as $crate::DeviceEvent>::unicode_released(unicode),
363                )
364            }
365        }
366
367        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
368        #[doc(hidden)]
369        impl InputTransaction {
370            #[wasm_bindgen(constructor)]
371            pub fn create() -> Self {
372                Self(<<$api as $crate::RemoteDesktopApi>::InputTransaction as $crate::InputTransaction>::create())
373            }
374
375            #[wasm_bindgen(js_name = addEvent)]
376            pub fn add_event(&mut self, event: DeviceEvent) {
377                $crate::InputTransaction::add_event(&mut self.0, event.0);
378            }
379        }
380
381        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
382        #[doc(hidden)]
383        impl ClipboardData {
384            #[wasm_bindgen(constructor)]
385            pub fn create() -> Self {
386                Self(<<$api as $crate::RemoteDesktopApi>::ClipboardData as $crate::ClipboardData>::create())
387            }
388
389            #[wasm_bindgen(js_name = addText)]
390            pub fn add_text(&mut self, mime_type: &str, text: &str) {
391                $crate::ClipboardData::add_text(&mut self.0, mime_type, text);
392            }
393
394            #[wasm_bindgen(js_name = addBinary)]
395            pub fn add_binary(&mut self, mime_type: &str, binary: &[u8]) {
396                $crate::ClipboardData::add_binary(&mut self.0, mime_type, binary);
397            }
398
399            pub fn items(&self) -> Vec<ClipboardItem> {
400                $crate::ClipboardData::items(&self.0)
401                    .into_iter()
402                    .cloned()
403                    .map(ClipboardItem)
404                    .collect()
405            }
406
407            #[wasm_bindgen(js_name = isEmpty)]
408            pub fn is_empty(&self) -> bool {
409                $crate::ClipboardData::is_empty(&self.0)
410            }
411        }
412
413        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
414        #[doc(hidden)]
415        impl ClipboardItem {
416            #[wasm_bindgen(js_name = mimeType)]
417            pub fn mime_type(&self) -> String {
418                $crate::ClipboardItem::mime_type(&self.0).to_owned()
419            }
420
421            pub fn value(&self) -> $crate::internal::wasm_bindgen::JsValue {
422                $crate::ClipboardItem::value(&self.0).into()
423            }
424        }
425
426        #[$crate::internal::wasm_bindgen::prelude::wasm_bindgen]
427        #[doc(hidden)]
428        impl IronError {
429            pub fn backtrace(&self) -> String {
430                $crate::IronError::backtrace(&self.0)
431            }
432
433            pub fn kind(&self) -> $crate::IronErrorKind {
434                $crate::IronError::kind(&self.0)
435            }
436        }
437    };
438}
439
440#[doc(hidden)]
441pub mod internal {
442    #[doc(hidden)]
443    pub use web_sys;
444
445    #[doc(hidden)]
446    pub use wasm_bindgen;
447
448    #[doc(hidden)]
449    pub fn setup(log_level: &str) {
450        // When the `console_error_panic_hook` feature is enabled, we can call the
451        // `set_panic_hook` function at least once during initialization, and then
452        // we will get better error messages if our code ever panics.
453        //
454        // For more details see
455        // https://github.com/rustwasm/console_error_panic_hook#readme
456        #[cfg(feature = "panic_hook")]
457        console_error_panic_hook::set_once();
458
459        if let Ok(level) = log_level.parse::<tracing::Level>() {
460            set_logger_once(level);
461        }
462    }
463
464    fn set_logger_once(level: tracing::Level) {
465        use tracing_subscriber::filter::LevelFilter;
466        use tracing_subscriber::fmt::time::UtcTime;
467        use tracing_subscriber::prelude::*;
468        use tracing_web::MakeConsoleWriter;
469
470        static INIT: std::sync::Once = std::sync::Once::new();
471
472        INIT.call_once(|| {
473            let fmt_layer = tracing_subscriber::fmt::layer()
474                .with_ansi(false)
475                .with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
476                .with_writer(MakeConsoleWriter);
477
478            let level_filter = LevelFilter::from_level(level);
479
480            tracing_subscriber::registry().with(fmt_layer).with(level_filter).init();
481        })
482    }
483}