rdesktop_core/config.rs
1//! Application configuration for rdesktop.
2//!
3//! This module defines all configuration types used by the framework.
4//! Configuration can be loaded from `rdesktop.toml` or constructed programmatically.
5//!
6//! ## Agent-First Development
7//!
8//! rdesktop supports a special `dev` mode designed for AI agent workflows:
9//! - `rdesktop dev` starts a local HTTP server serving the frontend
10//! - The app runs in the user's browser (localhost:PORT)
11//! - AI agents can use Playwright/Puppeteer MCP tools to inspect and interact
12//! - No native window needed during development
13//! - Same code works in both dev (browser) and prod (native window) modes
14
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18/// Configuration for the entire rdesktop application.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AppConfig {
21 /// Application identifier (reverse domain notation, e.g. "com.example.myapp")
22 pub identifier: String,
23
24 /// Application name displayed to users
25 pub name: String,
26
27 /// Application version
28 pub version: String,
29
30 /// Renderer to use (default: WebView)
31 #[serde(default)]
32 pub renderer: RendererConfig,
33
34 /// Window configuration
35 #[serde(default)]
36 pub window: WindowConfig,
37
38 /// Development server configuration (Agent-first)
39 #[serde(default)]
40 pub dev: DevConfig,
41
42 /// Bundle configuration for packaging
43 #[serde(default)]
44 pub bundle: BundleConfig,
45
46 /// Custom IPC command handlers
47 #[serde(default)]
48 pub commands: HashMap<String, CommandConfig>,
49
50 /// Global hotkeys registered at the OS level (fire even when the window is
51 /// unfocused). Each entry parses its `combo` via `Hotkey::from_str`
52 /// (e.g. "Ctrl+Shift+K", "Alt+F4", "Meta+Space").
53 #[serde(default)]
54 pub hotkeys: Vec<HotkeyConfig>,
55
56 /// Global input hook configuration (system-wide keyboard/mouse capture).
57 /// Off by default — enable explicitly to observe raw input events.
58 #[serde(default)]
59 pub global_input: GlobalInputConfig,
60}
61
62impl Default for AppConfig {
63 fn default() -> Self {
64 Self {
65 identifier: "com.example.app".to_string(),
66 name: "rdesktop App".to_string(),
67 version: "0.1.0".to_string(),
68 renderer: RendererConfig::default(),
69 window: WindowConfig::default(),
70 dev: DevConfig::default(),
71 bundle: BundleConfig::default(),
72 commands: HashMap::new(),
73 hotkeys: Vec::new(),
74 global_input: GlobalInputConfig::default(),
75 }
76 }
77}
78
79/// Renderer backend selection.
80///
81/// - `WebView`: Uses system WebView (WebView2/WebKit). Lightweight (~5MB).
82/// - `Chrome`: Uses Chrome Embedded Framework. Pixel-perfect (~150MB).
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub enum RendererKind {
85 /// Use system WebView (WebView2 on Windows, WebKit on macOS/Linux)
86 /// Default, lightweight, ~5MB overhead
87 #[serde(rename = "webview")]
88 WebView,
89
90 /// Use Chrome Embedded Framework for cross-platform pixel consistency
91 /// Larger bundle (~150MB) but guaranteed identical rendering
92 #[serde(rename = "chrome")]
93 Chrome,
94}
95
96impl Default for RendererKind {
97 fn default() -> Self {
98 Self::WebView
99 }
100}
101
102/// Renderer configuration (deserialized from `[renderer]` section in TOML).
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct RendererConfig {
105 /// Which renderer backend to use
106 #[serde(default)]
107 pub kind: RendererKind,
108
109 /// Enable WebGPU in the web context so the frontend can drive native
110 /// shaders (Wallpaper-Engine-style effects). Passed as Chromium flags.
111 #[serde(default = "default_true")]
112 pub webgpu: bool,
113}
114
115impl Default for RendererConfig {
116 fn default() -> Self {
117 Self {
118 kind: RendererKind::default(),
119 webgpu: true,
120 }
121 }
122}
123
124/// Development server configuration.
125///
126/// This is the core of rdesktop's Agent-first development story.
127/// During `rdesktop dev`, the app is served as a web page that AI agents
128/// can interact with using mature browser automation tools (Playwright, Puppeteer).
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct DevConfig {
131 /// Port for the development server (default: 1420, same as Tauri)
132 #[serde(default = "default_dev_port")]
133 pub port: u16,
134
135 /// Host to bind to (default: "localhost")
136 /// Use "0.0.0.0" to allow remote agent access
137 #[serde(default = "default_dev_host")]
138 pub host: String,
139
140 /// Whether to open the browser automatically
141 #[serde(default = "default_true")]
142 pub open_browser: bool,
143
144 /// Enable hot reload on file changes
145 #[serde(default = "default_true")]
146 pub hot_reload: bool,
147
148 /// Enable the Agent MCP endpoint for structured interaction
149 /// When enabled, exposes /__rdesktop__/agent/* endpoints for:
150 /// - DOM inspection (without screenshots)
151 /// - Element querying (CSS selectors, text content)
152 /// - Action execution (click, type, scroll)
153 /// - State snapshots (full DOM + computed styles)
154 #[serde(default = "default_true")]
155 pub agent_mode: bool,
156
157 /// Enable the devtools overlay in browser mode
158 #[serde(default = "default_true")]
159 pub devtools: bool,
160}
161
162impl Default for DevConfig {
163 fn default() -> Self {
164 Self {
165 port: default_dev_port(),
166 host: default_dev_host(),
167 open_browser: true,
168 hot_reload: true,
169 agent_mode: true,
170 devtools: true,
171 }
172 }
173}
174
175/// Window layer/kind for native mode.
176///
177/// Extends Tauri v2's model with a `Wallpaper` layer (desktop-level,
178/// click-through) and an `Overlay` layer (always-on-top HUD), enabling
179/// Wallpaper-Engine-style and HUD/PIP scenarios.
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
181pub enum WindowKind {
182 /// Standard application window (default)
183 #[serde(rename = "normal")]
184 Normal,
185
186 /// Always-on-top overlay (HUD / PIP / floating toolbar)
187 #[serde(rename = "overlay")]
188 Overlay,
189
190 /// Desktop wallpaper layer: sits behind icons, clicks pass through.
191 /// Implies `click_through = true`.
192 #[serde(rename = "wallpaper")]
193 Wallpaper,
194}
195
196impl Default for WindowKind {
197 fn default() -> Self {
198 Self::Normal
199 }
200}
201
202/// Window configuration for native mode.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct WindowConfig {
205 /// Window title
206 #[serde(default = "default_title")]
207 pub title: String,
208
209 /// Window width in logical pixels
210 #[serde(default = "default_width")]
211 pub width: u32,
212
213 /// Window height in logical pixels
214 #[serde(default = "default_height")]
215 pub height: u32,
216
217 /// Whether the window is resizable
218 #[serde(default = "default_true")]
219 pub resizable: bool,
220
221 /// Whether the window has decorations (title bar, borders)
222 #[serde(default = "default_true")]
223 pub decorations: bool,
224
225 /// Whether the window is transparent
226 #[serde(default)]
227 pub transparent: bool,
228
229 /// Whether the window is always on top
230 #[serde(default)]
231 pub always_on_top: bool,
232
233 /// Whether to show in taskbar
234 #[serde(default = "default_true")]
235 pub visible_on_all_workspaces: bool,
236
237 /// Window layer/kind. See [`WindowKind`].
238 #[serde(default)]
239 pub kind: WindowKind,
240
241 /// Click-through: pointer events fall through to whatever is behind the
242 /// window. Required for wallpaper; can also be set explicitly on overlays.
243 #[serde(default)]
244 pub click_through: bool,
245
246 /// Minimum window size (width, height)
247 pub min_size: Option<(u32, u32)>,
248
249 /// Maximum window size (width, height)
250 pub max_size: Option<(u32, u32)>,
251}
252
253impl Default for WindowConfig {
254 fn default() -> Self {
255 Self {
256 title: default_title(),
257 width: default_width(),
258 height: default_height(),
259 resizable: default_true(),
260 decorations: default_true(),
261 transparent: false,
262 always_on_top: false,
263 visible_on_all_workspaces: true,
264 kind: WindowKind::default(),
265 click_through: false,
266 min_size: None,
267 max_size: None,
268 }
269 }
270}
271
272/// Bundle configuration for packaging the app.
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct BundleConfig {
275 /// Windows installer format: "nsis", "wix", or "both"
276 #[serde(default = "default_windows_installer")]
277 pub windows_installer: String,
278
279 /// macOS bundle identifier
280 pub macos_bundle_id: Option<String>,
281
282 /// Linux package formats: "appimage", "deb", "rpm", or combinations
283 #[serde(default = "default_linux_packages")]
284 pub linux_packages: Vec<String>,
285
286 /// Icon path (relative to project root)
287 pub icon: Option<String>,
288
289 /// Whether to code-sign on macOS
290 #[serde(default)]
291 pub macos_sign: bool,
292
293 /// Whether to create a DMG on macOS
294 #[serde(default = "default_true")]
295 pub macos_dmg: bool,
296
297 /// Copyright notice
298 pub copyright: Option<String>,
299}
300
301impl Default for BundleConfig {
302 fn default() -> Self {
303 Self {
304 windows_installer: default_windows_installer(),
305 macos_bundle_id: None,
306 linux_packages: default_linux_packages(),
307 icon: None,
308 macos_sign: false,
309 macos_dmg: true,
310 copyright: None,
311 }
312 }
313}
314
315/// Configuration for an IPC command handler.
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct CommandConfig {
318 /// Command to execute
319 pub command: String,
320
321 /// Working directory (optional)
322 pub working_dir: Option<String>,
323
324 /// Environment variables
325 #[serde(default)]
326 pub env: HashMap<String, String>,
327}
328
329/// A global hotkey declared in configuration.
330///
331/// `id` is an optional stable identifier echoed back to the handler; `combo`
332/// is parsed by [`crate::hotkeys::Hotkey::from_str`] (case-insensitive,
333/// `+`-separated modifiers, e.g. `"Ctrl+Shift+K"`).
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct HotkeyConfig {
336 /// Stable identifier for this hotkey (echoed to the handler). If omitted,
337 /// the list index is used.
338 #[serde(default)]
339 pub id: Option<String>,
340
341 /// Key combination string, e.g. "Alt+F4", "Meta+Shift+P".
342 pub combo: String,
343
344 /// Optional human-readable title (shown in UI lists).
345 #[serde(default)]
346 pub title: Option<String>,
347}
348
349/// Global input hook configuration.
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct GlobalInputConfig {
352 /// Master switch for global input capture.
353 #[serde(default)]
354 pub enabled: bool,
355
356 /// Capture keyboard events.
357 #[serde(default = "default_true")]
358 pub keyboard: bool,
359
360 /// Capture mouse button events.
361 #[serde(default = "default_true")]
362 pub mouse: bool,
363
364 /// Also forward high-frequency `MouseMove` events (off by default).
365 #[serde(default)]
366 pub mouse_move: bool,
367}
368
369impl Default for GlobalInputConfig {
370 fn default() -> Self {
371 Self {
372 enabled: false,
373 keyboard: true,
374 mouse: true,
375 mouse_move: false,
376 }
377 }
378}
379
380// Default value functions for serde
381fn default_title() -> String {
382 "rdesktop App".to_string()
383}
384fn default_width() -> u32 {
385 1280
386}
387fn default_height() -> u32 {
388 720
389}
390fn default_true() -> bool {
391 true
392}
393fn default_dev_port() -> u16 {
394 1420
395}
396fn default_dev_host() -> String {
397 "localhost".to_string()
398}
399fn default_windows_installer() -> String {
400 "nsis".to_string()
401}
402fn default_linux_packages() -> Vec<String> {
403 vec!["appimage".to_string()]
404}