rdesktop_core/
renderer.rs1use crate::config::{RendererConfig, RendererKind as ConfigRendererKind, WindowConfig};
2use crate::ipc::IpcHandler;
3use crate::window::WindowHandle;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum RendererKind {
8 WebView,
10 Chrome,
12}
13
14impl From<&ConfigRendererKind> for RendererKind {
15 fn from(kind: &ConfigRendererKind) -> Self {
16 match kind {
17 ConfigRendererKind::WebView => Self::WebView,
18 ConfigRendererKind::Chrome => Self::Chrome,
19 }
20 }
21}
22
23impl From<&RendererConfig> for RendererKind {
24 fn from(config: &RendererConfig) -> Self {
25 Self::from(&config.kind)
26 }
27}
28
29pub trait Renderer {
35 fn init(&mut self) -> crate::Result<()>;
37
38 fn create_window(&mut self, config: &WindowConfig) -> crate::Result<WindowHandle>;
40
41 fn load_url(&self, window: WindowHandle, url: &str) -> crate::Result<()>;
43
44 fn load_html(&self, window: WindowHandle, html: &str) -> crate::Result<()>;
46
47 fn load_html_with_base_url(
56 &self,
57 window: WindowHandle,
58 html: &str,
59 base_url: &str,
60 ) -> crate::Result<()> {
61 let html_with_base = html_with_base_url(html, base_url);
62 self.load_html(window, &html_with_base)
63 }
64
65 fn eval_script(&self, window: WindowHandle, script: &str) -> crate::Result<()>;
67
68 fn set_ipc_handler(&mut self, handler: Box<dyn IpcHandler>);
70
71 fn send_to_frontend(&self, window: WindowHandle, message: &str) -> crate::Result<()>;
73
74 fn set_title(&self, window: WindowHandle, title: &str) -> crate::Result<()>;
76
77 fn set_size(&self, window: WindowHandle, width: u32, height: u32) -> crate::Result<()>;
79
80 fn set_resizable(&self, window: WindowHandle, resizable: bool) -> crate::Result<()>;
82
83 fn set_visible(&self, window: WindowHandle, visible: bool) -> crate::Result<()>;
85
86 fn close_window(&mut self, window: WindowHandle) -> crate::Result<()>;
88
89 fn minimize_window(&self, window: WindowHandle) -> crate::Result<()>;
93
94 fn maximize_window(&self, window: WindowHandle) -> crate::Result<()>;
96
97 fn is_maximized(&self, window: WindowHandle) -> crate::Result<bool>;
99
100 fn set_fullscreen(&self, window: WindowHandle, fullscreen: bool) -> crate::Result<()>;
102
103 fn is_fullscreen(&self, window: WindowHandle) -> crate::Result<bool>;
105
106 fn start_drag(&self, window: WindowHandle) -> crate::Result<()>;
111
112 fn start_resize(&self, window: WindowHandle, edge: ResizeEdge) -> crate::Result<()>;
116
117 fn set_decorations(&self, window: WindowHandle, decorations: bool) -> crate::Result<()>;
119
120 fn set_always_on_top(&self, window: WindowHandle, always: bool) -> crate::Result<()>;
122
123 fn set_click_through(&self, _window: WindowHandle, _enabled: bool) -> crate::Result<()> {
131 Ok(())
132 }
133
134 fn run(self: Box<Self>) -> crate::Result<()>;
138
139 fn kind(&self) -> RendererKind;
141}
142
143fn html_with_base_url(html: &str, base_url: &str) -> String {
145 let lower = html.to_ascii_lowercase();
146 if lower.contains("<base ") || lower.contains("<base>") {
147 return html.to_string();
148 }
149
150 let escaped_base = base_url
151 .replace('&', "&")
152 .replace('"', """)
153 .replace('<', "<")
154 .replace('>', ">");
155 let normalized_base = if escaped_base.ends_with('/') {
156 escaped_base
157 } else {
158 format!("{escaped_base}/")
159 };
160 let base_tag = format!(r#"<base href="{normalized_base}">"#);
161
162 if let Some(head_start) = lower.find("<head") {
163 if let Some(tag_end) = html[head_start..].find('>') {
164 let insert_at = head_start + tag_end + 1;
165 let mut output = String::with_capacity(html.len() + base_tag.len() + 1);
166 output.push_str(&html[..insert_at]);
167 output.push('\n');
168 output.push_str(&base_tag);
169 output.push_str(&html[insert_at..]);
170 return output;
171 }
172 }
173
174 format!("{base_tag}\n{html}")
175}
176
177#[cfg(test)]
178mod tests {
179 use super::html_with_base_url;
180
181 #[test]
182 fn injects_base_into_head() {
183 let html = "<!doctype html><html><head><title>Test</title></head></html>";
184 let result = html_with_base_url(html, "file:///C:/app/frontend");
185 assert!(result.contains(r#"<base href="file:///C:/app/frontend/">"#));
186 assert!(result.contains("<head>\n<base"));
187 }
188
189 #[test]
190 fn preserves_existing_base() {
191 let html = r#"<head><base href="custom://app/"></head>"#;
192 assert_eq!(html_with_base_url(html, "file:///ignored"), html);
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum ResizeEdge {
199 Top,
200 Bottom,
201 Left,
202 Right,
203 TopLeft,
204 TopRight,
205 BottomLeft,
206 BottomRight,
207}