1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex};
4
5use rdesktop_core::config::{AppConfig, WindowConfig};
6use rdesktop_core::ipc::{IpcHandler, IpcMessage};
7use rdesktop_core::renderer::{Renderer, RendererKind, ResizeEdge};
8use rdesktop_core::window::WindowHandle;
9use rdesktop_core::Result;
10
11use tao::event::{Event, StartCause, WindowEvent};
12use tao::event_loop::{ControlFlow, EventLoopBuilder};
13use tao::window::{Window, WindowBuilder, WindowId};
14#[cfg(target_os = "windows")]
15use wry::WebViewBuilderExtWindows;
16use wry::{WebView, WebViewBuilder};
17
18struct WindowEntry {
19 window: Window,
20 webview: WebView,
21}
22
23enum PendingOp {
25 LoadUrl(u64, String),
26 LoadHtml(u64, String),
27 EvalScript(u64, String),
28 SetTitle(u64, String),
29 SetSize(u64, u32, u32),
30 SetResizable(u64, bool),
31 SetVisible(u64, bool),
32 SendToFrontend(u64, String),
33 Close(u64),
34 Minimize(u64),
36 Maximize(u64),
37 SetFullscreen(u64, bool),
38 StartDrag(u64),
39 StartResize(u64, tao::window::ResizeDirection),
40 SetDecorations(u64, bool),
41 SetAlwaysOnTop(u64, bool),
42}
43
44type IpcResponseQueue = Arc<Mutex<Vec<String>>>;
46
47type WindowCommandQueue = Arc<Mutex<Vec<WindowCommand>>>;
49
50struct WindowCommand {
52 rdesktop_id: u64,
53 action: WindowAction,
54}
55
56enum WindowAction {
57 Minimize,
58 Maximize,
59 Close,
60 StartDrag,
61 StartResize(tao::window::ResizeDirection),
62 SetFullscreen(bool),
63}
64
65fn to_tao_resize(edge: ResizeEdge) -> tao::window::ResizeDirection {
67 match edge {
68 ResizeEdge::Top => tao::window::ResizeDirection::North,
69 ResizeEdge::Bottom => tao::window::ResizeDirection::South,
70 ResizeEdge::Left => tao::window::ResizeDirection::West,
71 ResizeEdge::Right => tao::window::ResizeDirection::East,
72 ResizeEdge::TopLeft => tao::window::ResizeDirection::NorthWest,
73 ResizeEdge::TopRight => tao::window::ResizeDirection::NorthEast,
74 ResizeEdge::BottomLeft => tao::window::ResizeDirection::SouthWest,
75 ResizeEdge::BottomRight => tao::window::ResizeDirection::SouthEast,
76 }
77}
78
79pub struct WebViewRenderer {
99 _config: AppConfig,
100 ipc_handler: Option<Arc<dyn IpcHandler>>,
101 pending_windows: RefCell<Vec<(u64, WindowConfig)>>,
102 pending_ops: RefCell<Vec<PendingOp>>,
103 next_window_id: RefCell<u64>,
104 outbox: Arc<Mutex<Vec<String>>>,
108}
109
110impl WebViewRenderer {
111 pub fn new(config: &AppConfig) -> Result<Self> {
112 Ok(Self {
113 _config: config.clone(),
114 ipc_handler: None,
115 pending_windows: RefCell::new(Vec::new()),
116 pending_ops: RefCell::new(Vec::new()),
117 next_window_id: RefCell::new(1),
118 outbox: Arc::new(Mutex::new(Vec::new())),
119 })
120 }
121
122 pub fn set_outbox(&mut self, outbox: Arc<Mutex<Vec<String>>>) {
126 self.outbox = outbox;
127 }
128
129 fn next_id(&self) -> u64 {
130 let mut id = self.next_window_id.borrow_mut();
131 let current = *id;
132 *id += 1;
133 current
134 }
135
136 fn bridge_script() -> &'static str {
138 r#"
139 (function() {
140 if (window.__RDESKTOP_BRIDGE__) return;
141 window.__RDESKTOP_BRIDGE__ = true;
142 window.__RDESKTOP_RESOLVE__ = {};
143
144 // ── IPC Bridge ──────────────────────────────────────
145 window.__RDESKTOP_INVOKE__ = function(cmd, payload) {
146 return new Promise(function(resolve, reject) {
147 var id = Math.random().toString(36).slice(2);
148 window.__RDESKTOP_RESOLVE__[id] = resolve;
149 if (window.ipc && window.ipc.postMessage) {
150 window.ipc.postMessage(JSON.stringify({ id: id, cmd: cmd, payload: payload || {} }));
151 }
152 setTimeout(function() {
153 if (window.__RDESKTOP_RESOLVE__[id]) {
154 delete window.__RDESKTOP_RESOLVE__[id];
155 reject(new Error('IPC timeout'));
156 }
157 }, 30000);
158 });
159 };
160
161 window.__RDESKTOP_IPC__ = function(message) {
162 try {
163 var data = typeof message === 'string' ? JSON.parse(message) : message;
164 if (data.id && window.__RDESKTOP_RESOLVE__[data.id]) {
165 window.__RDESKTOP_RESOLVE__[data.id](data);
166 delete window.__RDESKTOP_RESOLVE__[data.id];
167 } else if (window.__RDESKTOP_PUSH__) {
168 // Unnamed push (e.g. extension host → UI event).
169 window.__RDESKTOP_PUSH__(data);
170 }
171 } catch (e) {
172 console.error('rdesktop IPC error:', e);
173 }
174 };
175
176 // ── Window Controls (frameless / custom title bar) ──
177 window.__RDESKTOP_WINDOW__ = {
178 minimize: function() {
179 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'minimize' }));
180 },
181 maximize: function() {
182 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'maximize' }));
183 },
184 close: function() {
185 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'close' }));
186 },
187 startDrag: function() {
188 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'start_drag' }));
189 },
190 startResize: function(edge) {
191 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'start_resize', edge: edge || 'bottom-right' }));
192 },
193 setFullscreen: function(fs) {
194 window.ipc && window.ipc.postMessage(JSON.stringify({ __window__: true, action: 'set_fullscreen', value: !!fs }));
195 },
196 isMaximized: false,
197 isFullscreen: false
198 };
199 })();
200 "#
201 }
202
203 fn parse_window_command(msg: &IpcMessage, rdesktop_id: u64) -> Option<WindowCommand> {
206 if msg
208 .payload
209 .get("__window__")
210 .and_then(|v| v.as_bool())
211 .unwrap_or(false)
212 {
213 let action = match msg.payload["action"].as_str()? {
214 "minimize" => WindowAction::Minimize,
215 "maximize" => WindowAction::Maximize,
216 "close" => WindowAction::Close,
217 "start_drag" => WindowAction::StartDrag,
218 "start_resize" => {
219 let edge_str = msg.payload["edge"].as_str().unwrap_or("bottom-right");
220 let dir = match edge_str {
221 "top" => tao::window::ResizeDirection::North,
222 "bottom" => tao::window::ResizeDirection::South,
223 "left" => tao::window::ResizeDirection::West,
224 "right" => tao::window::ResizeDirection::East,
225 "top-left" => tao::window::ResizeDirection::NorthWest,
226 "top-right" => tao::window::ResizeDirection::NorthEast,
227 "bottom-left" => tao::window::ResizeDirection::SouthWest,
228 _ => tao::window::ResizeDirection::SouthEast,
229 };
230 WindowAction::StartResize(dir)
231 }
232 "set_fullscreen" => {
233 let val = msg.payload["value"].as_bool().unwrap_or(false);
234 WindowAction::SetFullscreen(val)
235 }
236 _ => return None,
237 };
238 return Some(WindowCommand {
239 rdesktop_id,
240 action,
241 });
242 }
243 None
244 }
245}
246
247impl Renderer for WebViewRenderer {
248 fn init(&mut self) -> Result<()> {
249 tracing::info!("Initializing WebView renderer");
250 Ok(())
251 }
252
253 fn create_window(&mut self, config: &WindowConfig) -> Result<WindowHandle> {
254 let id = self.next_id();
255 self.pending_windows.borrow_mut().push((id, config.clone()));
256 tracing::info!(window_id = id, "Window queued for creation");
257 Ok(WindowHandle::new(id))
258 }
259
260 fn load_url(&self, window: WindowHandle, url: &str) -> Result<()> {
261 self.pending_ops
262 .borrow_mut()
263 .push(PendingOp::LoadUrl(window.id(), url.to_string()));
264 Ok(())
265 }
266
267 fn load_html(&self, window: WindowHandle, html: &str) -> Result<()> {
268 self.pending_ops
269 .borrow_mut()
270 .push(PendingOp::LoadHtml(window.id(), html.to_string()));
271 Ok(())
272 }
273
274 fn eval_script(&self, window: WindowHandle, script: &str) -> Result<()> {
275 self.pending_ops
276 .borrow_mut()
277 .push(PendingOp::EvalScript(window.id(), script.to_string()));
278 Ok(())
279 }
280
281 fn set_ipc_handler(&mut self, handler: Box<dyn IpcHandler>) {
282 self.ipc_handler = Some(Arc::from(handler));
283 }
284
285 fn send_to_frontend(&self, window: WindowHandle, message: &str) -> Result<()> {
286 self.pending_ops
287 .borrow_mut()
288 .push(PendingOp::SendToFrontend(window.id(), message.to_string()));
289 Ok(())
290 }
291
292 fn set_title(&self, window: WindowHandle, title: &str) -> Result<()> {
293 self.pending_ops
294 .borrow_mut()
295 .push(PendingOp::SetTitle(window.id(), title.to_string()));
296 Ok(())
297 }
298
299 fn set_size(&self, window: WindowHandle, width: u32, height: u32) -> Result<()> {
300 self.pending_ops
301 .borrow_mut()
302 .push(PendingOp::SetSize(window.id(), width, height));
303 Ok(())
304 }
305
306 fn set_resizable(&self, window: WindowHandle, resizable: bool) -> Result<()> {
307 self.pending_ops
308 .borrow_mut()
309 .push(PendingOp::SetResizable(window.id(), resizable));
310 Ok(())
311 }
312
313 fn set_visible(&self, window: WindowHandle, visible: bool) -> Result<()> {
314 self.pending_ops
315 .borrow_mut()
316 .push(PendingOp::SetVisible(window.id(), visible));
317 Ok(())
318 }
319
320 fn close_window(&mut self, window: WindowHandle) -> Result<()> {
321 self.pending_ops
322 .borrow_mut()
323 .push(PendingOp::Close(window.id()));
324 Ok(())
325 }
326
327 fn minimize_window(&self, window: WindowHandle) -> Result<()> {
330 self.pending_ops
331 .borrow_mut()
332 .push(PendingOp::Minimize(window.id()));
333 Ok(())
334 }
335
336 fn maximize_window(&self, window: WindowHandle) -> Result<()> {
337 self.pending_ops
338 .borrow_mut()
339 .push(PendingOp::Maximize(window.id()));
340 Ok(())
341 }
342
343 fn is_maximized(&self, _window: WindowHandle) -> Result<bool> {
344 Ok(false)
347 }
348
349 fn set_fullscreen(&self, window: WindowHandle, fullscreen: bool) -> Result<()> {
350 self.pending_ops
351 .borrow_mut()
352 .push(PendingOp::SetFullscreen(window.id(), fullscreen));
353 Ok(())
354 }
355
356 fn is_fullscreen(&self, _window: WindowHandle) -> Result<bool> {
357 Ok(false)
358 }
359
360 fn start_drag(&self, window: WindowHandle) -> Result<()> {
361 self.pending_ops
362 .borrow_mut()
363 .push(PendingOp::StartDrag(window.id()));
364 Ok(())
365 }
366
367 fn start_resize(&self, window: WindowHandle, edge: ResizeEdge) -> Result<()> {
368 self.pending_ops
369 .borrow_mut()
370 .push(PendingOp::StartResize(window.id(), to_tao_resize(edge)));
371 Ok(())
372 }
373
374 fn set_decorations(&self, window: WindowHandle, decorations: bool) -> Result<()> {
375 self.pending_ops
376 .borrow_mut()
377 .push(PendingOp::SetDecorations(window.id(), decorations));
378 Ok(())
379 }
380
381 fn set_always_on_top(&self, window: WindowHandle, always: bool) -> Result<()> {
382 self.pending_ops
383 .borrow_mut()
384 .push(PendingOp::SetAlwaysOnTop(window.id(), always));
385 Ok(())
386 }
387
388 fn run(mut self: Box<Self>) -> Result<()> {
391 tracing::info!("Starting WebView event loop");
392
393 let ipc_handler = self.ipc_handler.take();
394 let webgpu_enabled = self._config.renderer.webgpu;
395 let pending_windows: Vec<(u64, WindowConfig)> =
396 self.pending_windows.borrow_mut().drain(..).collect();
397 let pending_ops: Vec<PendingOp> = self.pending_ops.borrow_mut().drain(..).collect();
398
399 let ipc_response_queue: IpcResponseQueue = Arc::new(Mutex::new(Vec::new()));
400 let ipc_queue_for_handler = ipc_response_queue.clone();
401
402 let outbox_for_loop = self.outbox.clone();
404
405 let global_handler = rdesktop_core::PushHandler::new(self.outbox.clone());
410 let _hotkey_manager = {
411 let mgr = rdesktop_core::HotkeyManager::new(global_handler.clone());
412 for (i, hc) in self._config.hotkeys.iter().enumerate() {
413 if let Ok(hk) = hc.combo.parse::<rdesktop_core::Hotkey>() {
414 let id = i as u32 + 1;
415 if let Err(e) = mgr.register(id, &hk) {
416 tracing::warn!("failed to register hotkey {:?}: {}", hc.combo, e);
417 }
418 } else {
419 tracing::warn!("invalid hotkey combo: {:?}", hc.combo);
420 }
421 }
422 mgr
423 };
424 let _input_manager = if self._config.global_input.enabled {
425 let mut inp = rdesktop_core::GlobalInput::new(global_handler.clone());
426 if self._config.global_input.mouse_move {
427 inp = inp.with_mouse_move(true);
428 }
429 match inp.start() {
430 Ok(()) => Some(inp),
431 Err(e) => {
432 tracing::warn!("failed to start global input: {}", e);
433 None
434 }
435 }
436 } else {
437 None
438 };
439
440 let window_cmd_queue: WindowCommandQueue = Arc::new(Mutex::new(Vec::new()));
442 let window_cmd_queue_for_ipc = window_cmd_queue.clone();
443
444 let first_window_id: Arc<Mutex<Option<u64>>> = Arc::new(Mutex::new(None));
447
448 let event_loop = EventLoopBuilder::new().build();
449 let mut windows: HashMap<WindowId, WindowEntry> = HashMap::new();
450 let mut rdesktop_to_tao: HashMap<u64, WindowId> = HashMap::new();
451 let mut tao_to_rdesktop: HashMap<WindowId, u64> = HashMap::new();
452
453 event_loop.run(move |event, event_loop_target, control_flow| {
454 *control_flow = ControlFlow::Wait;
455
456 match event {
457 Event::NewEvents(StartCause::Init) => {
458 for (rdesktop_id, window_config) in &pending_windows {
460 let window = match WindowBuilder::new()
461 .with_title(&window_config.title)
462 .with_inner_size(tao::dpi::LogicalSize::new(
463 window_config.width,
464 window_config.height,
465 ))
466 .with_resizable(window_config.resizable)
467 .with_decorations(window_config.decorations)
468 .with_transparent(window_config.transparent)
469 .with_always_on_top(window_config.always_on_top)
470 .build(event_loop_target)
471 {
472 Ok(w) => w,
473 Err(e) => {
474 tracing::error!("Failed to create window {}: {}", rdesktop_id, e);
475 continue;
476 }
477 };
478
479 let tao_id = window.id();
480
481 rdesktop_core::apply_window_attributes(&window, window_config);
483
484 let mut builder = WebViewBuilder::new()
485 .with_url("about:blank")
486 .with_devtools(cfg!(debug_assertions))
487 .with_initialization_script(Self::bridge_script());
488
489 if window_config.transparent {
492 builder = builder.with_transparent(true);
493 }
494 #[cfg(target_os = "windows")]
500 if webgpu_enabled {
501 builder = builder.with_additional_browser_args(
502 "--enable-features=Vulkan,WebGPU --enable-unsafe-webgpu",
503 );
504 }
505
506 if let Some(ref handler) = ipc_handler {
508 let handler = handler.clone();
509 let queue = ipc_queue_for_handler.clone();
510 let win_queue = window_cmd_queue_for_ipc.clone();
511 let _first_id = first_window_id.clone();
512 let rd_id = *rdesktop_id;
513
514 builder =
515 builder.with_ipc_handler(move |req: wry::http::Request<String>| {
516 let body = req.body();
517
518 if let Ok(msg) = serde_json::from_str::<IpcMessage>(body) {
520 if let Some(cmd) =
521 WebViewRenderer::parse_window_command(&msg, rd_id)
522 {
523 if let Ok(mut q) = win_queue.lock() {
524 q.push(cmd);
525 }
526 return;
527 }
528
529 let response = handler.handle(msg);
531 if let Ok(json) = serde_json::to_string(&response) {
532 if let Ok(mut q) = queue.lock() {
533 q.push(json);
534 }
535 }
536 }
537 });
538 }
539
540 let webview = match builder.build(&window) {
541 Ok(wv) => wv,
542 Err(e) => {
543 tracing::error!("Failed to create webview {}: {}", rdesktop_id, e);
544 continue;
545 }
546 };
547
548 windows.insert(tao_id, WindowEntry { window, webview });
549 rdesktop_to_tao.insert(*rdesktop_id, tao_id);
550 tao_to_rdesktop.insert(tao_id, *rdesktop_id);
551
552 if first_window_id.lock().unwrap().is_none() {
553 *first_window_id.lock().unwrap() = Some(*rdesktop_id);
554 }
555
556 tracing::info!(rdesktop_id = rdesktop_id, ?tao_id, "Window created");
557 }
558
559 for op in &pending_ops {
561 Self::apply_op(op, &windows, &rdesktop_to_tao);
562 }
563 }
564
565 Event::WindowEvent {
566 event: WindowEvent::CloseRequested,
567 window_id,
568 ..
569 } => {
570 if let Some(rd_id) = tao_to_rdesktop.remove(&window_id) {
571 rdesktop_to_tao.remove(&rd_id);
572 }
573 windows.remove(&window_id);
574 if windows.is_empty() {
575 tracing::info!("All windows closed, exiting");
576 *control_flow = ControlFlow::Exit;
577 }
578 }
579
580 Event::WindowEvent {
581 event: WindowEvent::Resized(size),
582 window_id,
583 ..
584 } => {
585 if let Some(entry) = windows.get(&window_id) {
586 let _ = entry.webview.set_bounds(wry::Rect {
587 position: tao::dpi::LogicalPosition::<i32>::new(0, 0).into(),
588 size: tao::dpi::LogicalSize::new(size.width, size.height).into(),
589 });
590 }
591 }
592
593 Event::WindowEvent {
594 event: WindowEvent::ScaleFactorChanged { new_inner_size, .. },
595 window_id,
596 ..
597 } => {
598 if let Some(entry) = windows.get(&window_id) {
599 let _ = entry.webview.set_bounds(wry::Rect {
600 position: tao::dpi::LogicalPosition::<i32>::new(0, 0).into(),
601 size: tao::dpi::LogicalSize::new(
602 new_inner_size.width,
603 new_inner_size.height,
604 )
605 .into(),
606 });
607 }
608 }
609
610 Event::MainEventsCleared => {
611 let responses: Vec<String> = {
613 let mut queue = ipc_response_queue.lock().unwrap();
614 queue.drain(..).collect()
615 };
616 for json in responses {
617 if let Some(entry) = windows.values().next() {
618 if let Ok(js) = serde_json::to_string(&json) {
619 let script = format!("window.__RDESKTOP_IPC__({js})");
620 let _ = entry.webview.evaluate_script(&script);
621 }
622 }
623 }
624
625 let outbox_msgs: Vec<String> = {
627 let mut queue = outbox_for_loop.lock().unwrap();
628 queue.drain(..).collect()
629 };
630 for json in outbox_msgs {
631 if let Some(entry) = windows.values().next() {
632 if let Ok(js) = serde_json::to_string(&json) {
633 let script = format!("window.__RDESKTOP_IPC__({js})");
634 let _ = entry.webview.evaluate_script(&script);
635 }
636 }
637 }
638
639 let commands: Vec<WindowCommand> = {
641 let mut queue = window_cmd_queue.lock().unwrap();
642 queue.drain(..).collect()
643 };
644 for cmd in commands {
645 if let Some(tao_id) = rdesktop_to_tao.get(&cmd.rdesktop_id) {
646 if let Some(entry) = windows.get(tao_id) {
647 match cmd.action {
648 WindowAction::Minimize => {
649 entry.window.set_minimized(true);
650 }
651 WindowAction::Maximize => {
652 let is_max = entry.window.is_maximized();
653 entry.window.set_maximized(!is_max);
654 }
655 WindowAction::Close => {
656 }
659 WindowAction::StartDrag => {
660 let _ = entry.window.drag_window();
661 }
662 WindowAction::StartResize(dir) => {
663 let _ = entry.window.drag_resize_window(dir);
664 }
665 WindowAction::SetFullscreen(fs) => {
666 if fs {
667 entry.window.set_fullscreen(Some(
668 tao::window::Fullscreen::Borderless(None),
669 ));
670 } else {
671 entry.window.set_fullscreen(None);
672 }
673 }
674 }
675 }
676 }
677 }
678 }
679
680 Event::LoopDestroyed => {
681 tracing::info!("WebView event loop destroyed");
682 }
683
684 _ => {}
685 }
686 });
687 }
688
689 fn kind(&self) -> RendererKind {
690 RendererKind::WebView
691 }
692}
693
694impl WebViewRenderer {
695 fn apply_op(
697 op: &PendingOp,
698 windows: &HashMap<WindowId, WindowEntry>,
699 rdesktop_to_tao: &HashMap<u64, WindowId>,
700 ) {
701 match op {
702 PendingOp::LoadUrl(rd_id, url) => {
703 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
704 let _ = entry.webview.load_url(url);
705 }
706 }
707 PendingOp::LoadHtml(rd_id, html) => {
708 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
709 let _ = entry.webview.load_html(html);
710 }
711 }
712 PendingOp::EvalScript(rd_id, script) => {
713 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
714 let _ = entry.webview.evaluate_script(script);
715 }
716 }
717 PendingOp::SetTitle(rd_id, title) => {
718 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
719 entry.window.set_title(title);
720 }
721 }
722 PendingOp::SetSize(rd_id, w, h) => {
723 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
724 entry
725 .window
726 .set_inner_size(tao::dpi::LogicalSize::new(*w, *h));
727 }
728 }
729 PendingOp::SetResizable(rd_id, resizable) => {
730 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
731 entry.window.set_resizable(*resizable);
732 }
733 }
734 PendingOp::SetVisible(rd_id, visible) => {
735 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
736 entry.window.set_visible(*visible);
737 }
738 }
739 PendingOp::SendToFrontend(rd_id, msg) => {
740 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
741 if let Ok(js) = serde_json::to_string(msg) {
742 let script = format!("window.__RDESKTOP_IPC__({js})");
743 let _ = entry.webview.evaluate_script(&script);
744 }
745 }
746 }
747 PendingOp::Close(_rd_id) => {
748 }
750 PendingOp::Minimize(rd_id) => {
751 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
752 entry.window.set_minimized(true);
753 }
754 }
755 PendingOp::Maximize(rd_id) => {
756 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
757 let is_max = entry.window.is_maximized();
758 entry.window.set_maximized(!is_max);
759 }
760 }
761 PendingOp::SetFullscreen(rd_id, fs) => {
762 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
763 if *fs {
764 entry
765 .window
766 .set_fullscreen(Some(tao::window::Fullscreen::Borderless(None)));
767 } else {
768 entry.window.set_fullscreen(None);
769 }
770 }
771 }
772 PendingOp::StartDrag(rd_id) => {
773 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
774 let _ = entry.window.drag_window();
775 }
776 }
777 PendingOp::StartResize(rd_id, dir) => {
778 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
779 let _ = entry.window.drag_resize_window(*dir);
780 }
781 }
782 PendingOp::SetDecorations(rd_id, decorations) => {
783 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
784 entry.window.set_decorations(*decorations);
785 }
786 }
787 PendingOp::SetAlwaysOnTop(rd_id, always) => {
788 if let Some(entry) = rdesktop_to_tao.get(rd_id).and_then(|id| windows.get(id)) {
789 entry.window.set_always_on_top(*always);
790 }
791 }
792 }
793 }
794}