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)]
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/// 32-bit RGBA window icon data used by native window backends.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct WindowIcon {
205    /// Pixels in row-major RGBA order.
206    pub rgba: Vec<u8>,
207    /// Icon width in pixels.
208    pub width: u32,
209    /// Icon height in pixels.
210    pub height: u32,
211}
212
213/// Window configuration for native mode.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct WindowConfig {
216    /// Window title
217    #[serde(default = "default_title")]
218    pub title: String,
219
220    /// Window width in logical pixels
221    #[serde(default = "default_width")]
222    pub width: u32,
223
224    /// Window height in logical pixels
225    #[serde(default = "default_height")]
226    pub height: u32,
227
228    /// Whether the window is resizable
229    #[serde(default = "default_true")]
230    pub resizable: bool,
231
232    /// Whether the window has decorations (title bar, borders)
233    #[serde(default = "default_true")]
234    pub decorations: bool,
235
236    /// Whether the window is transparent
237    #[serde(default)]
238    pub transparent: bool,
239
240    /// Whether the window is always on top
241    #[serde(default)]
242    pub always_on_top: bool,
243
244    /// Whether to show in taskbar
245    #[serde(default = "default_true")]
246    pub visible_on_all_workspaces: bool,
247
248    /// Optional native icon shown in the title bar, taskbar and background previews.
249    #[serde(default)]
250    pub icon: Option<WindowIcon>,
251
252    /// Window layer/kind. See [`WindowKind`].
253    #[serde(default)]
254    pub kind: WindowKind,
255
256    /// Click-through: pointer events fall through to whatever is behind the
257    /// window. Required for wallpaper; can also be set explicitly on overlays.
258    #[serde(default)]
259    pub click_through: bool,
260
261    /// Minimum window size (width, height)
262    pub min_size: Option<(u32, u32)>,
263
264    /// Maximum window size (width, height)
265    pub max_size: Option<(u32, u32)>,
266}
267
268impl Default for WindowConfig {
269    fn default() -> Self {
270        Self {
271            title: default_title(),
272            width: default_width(),
273            height: default_height(),
274            resizable: default_true(),
275            decorations: default_true(),
276            transparent: false,
277            always_on_top: false,
278            visible_on_all_workspaces: true,
279            icon: None,
280            kind: WindowKind::default(),
281            click_through: false,
282            min_size: None,
283            max_size: None,
284        }
285    }
286}
287
288/// Bundle configuration for packaging the app.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct BundleConfig {
291    /// Windows installer format: "nsis", "wix", or "both"
292    #[serde(default = "default_windows_installer")]
293    pub windows_installer: String,
294
295    /// macOS bundle identifier
296    pub macos_bundle_id: Option<String>,
297
298    /// Linux package formats: "appimage", "deb", "rpm", or combinations
299    #[serde(default = "default_linux_packages")]
300    pub linux_packages: Vec<String>,
301
302    /// Icon path (relative to project root)
303    pub icon: Option<String>,
304
305    /// Whether to code-sign on macOS
306    #[serde(default)]
307    pub macos_sign: bool,
308
309    /// Whether to create a DMG on macOS
310    #[serde(default = "default_true")]
311    pub macos_dmg: bool,
312
313    /// Copyright notice
314    pub copyright: Option<String>,
315}
316
317impl Default for BundleConfig {
318    fn default() -> Self {
319        Self {
320            windows_installer: default_windows_installer(),
321            macos_bundle_id: None,
322            linux_packages: default_linux_packages(),
323            icon: None,
324            macos_sign: false,
325            macos_dmg: true,
326            copyright: None,
327        }
328    }
329}
330
331/// Configuration for an IPC command handler.
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct CommandConfig {
334    /// Command to execute
335    pub command: String,
336
337    /// Working directory (optional)
338    pub working_dir: Option<String>,
339
340    /// Environment variables
341    #[serde(default)]
342    pub env: HashMap<String, String>,
343}
344
345/// A global hotkey declared in configuration.
346///
347/// `id` is an optional stable identifier echoed back to the handler; `combo`
348/// is parsed by [`crate::hotkeys::Hotkey::from_str`] (case-insensitive,
349/// `+`-separated modifiers, e.g. `"Ctrl+Shift+K"`).
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct HotkeyConfig {
352    /// Stable identifier for this hotkey (echoed to the handler). If omitted,
353    /// the list index is used.
354    #[serde(default)]
355    pub id: Option<String>,
356
357    /// Key combination string, e.g. "Alt+F4", "Meta+Shift+P".
358    pub combo: String,
359
360    /// Optional human-readable title (shown in UI lists).
361    #[serde(default)]
362    pub title: Option<String>,
363}
364
365/// Global input hook configuration.
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct GlobalInputConfig {
368    /// Master switch for global input capture.
369    #[serde(default)]
370    pub enabled: bool,
371
372    /// Capture keyboard events.
373    #[serde(default = "default_true")]
374    pub keyboard: bool,
375
376    /// Capture mouse button events.
377    #[serde(default = "default_true")]
378    pub mouse: bool,
379
380    /// Also forward high-frequency `MouseMove` events (off by default).
381    #[serde(default)]
382    pub mouse_move: bool,
383}
384
385impl Default for GlobalInputConfig {
386    fn default() -> Self {
387        Self {
388            enabled: false,
389            keyboard: true,
390            mouse: true,
391            mouse_move: false,
392        }
393    }
394}
395
396// Default value functions for serde
397fn default_title() -> String {
398    "rdesktop App".to_string()
399}
400fn default_width() -> u32 {
401    1280
402}
403fn default_height() -> u32 {
404    720
405}
406fn default_true() -> bool {
407    true
408}
409fn default_dev_port() -> u16 {
410    1420
411}
412fn default_dev_host() -> String {
413    "localhost".to_string()
414}
415fn default_windows_installer() -> String {
416    "nsis".to_string()
417}
418fn default_linux_packages() -> Vec<String> {
419    vec!["appimage".to_string()]
420}