Skip to main content

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