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 fn pre_setup() {}
32
33 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 #[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()) .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}