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 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 = 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 #[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()) .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}