diff --git a/config.example.toml b/config.example.toml index fb1667db..8f9c420b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -433,10 +433,10 @@ exit_after_capture = false # ═══════════════════════════════════════════════════════════════════════════════ [session] -# Enable persistence per mode (default to false for clean startup) -persist_transparent = false -persist_whiteboard = false -persist_blackboard = false +# Enable persistence per mode (default to true) +persist_transparent = true +persist_whiteboard = true +persist_blackboard = true # Persist undo/redo history alongside shapes (set false to save only visible drawings) persist_history = true diff --git a/configurator/README.md b/configurator/README.md index e3247ae0..f10ba9c2 100644 --- a/configurator/README.md +++ b/configurator/README.md @@ -14,6 +14,10 @@ cd configurator cargo run ``` +On GNOME Wayland, the configurator forces the `tiny-skia` renderer by default to +avoid wgpu dma-buf/present mode crashes. Override with +`ICED_BACKEND=wgpu wayscriber-configurator` if desired. + The window loads the current config, lets you tweak values across the tabbed sections, and writes changes back via `Config::save_with_backup()`. ### Handy actions diff --git a/configurator/src/app.rs b/configurator/src/app.rs index 9a6d3b1a..c165d924 100644 --- a/configurator/src/app.rs +++ b/configurator/src/app.rs @@ -25,14 +25,36 @@ pub fn run() -> iced::Result { settings.window.size = Size::new(960.0, 640.0); settings.window.resizable = true; settings.window.decorations = true; + if std::env::var_os("ICED_BACKEND").is_none() && should_force_tiny_skia() { + // GNOME Wayland + wgpu can crash on dma-buf/present mode selection; tiny-skia avoids this. + // SAFETY: setting a process-local env var before initializing iced is safe here. + unsafe { + std::env::set_var("ICED_BACKEND", "tiny-skia"); + } + eprintln!( + "wayscriber-configurator: GNOME Wayland detected; using tiny-skia renderer (set ICED_BACKEND=wgpu to override)." + ); + } ConfiguratorApp::run(settings) } +fn should_force_tiny_skia() -> bool { + if std::env::var_os("WAYLAND_DISPLAY").is_none() { + return false; + } + let current = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + let session = std::env::var("XDG_SESSION_DESKTOP").unwrap_or_default(); + let combined = format!("{current};{session}"); + let combined = combined.to_ascii_lowercase(); + combined.contains("gnome") || combined.contains("ubuntu") +} + #[derive(Debug)] pub struct ConfiguratorApp { draft: ConfigDraft, baseline: ConfigDraft, defaults: ConfigDraft, + base_config: Arc, status: StatusMessage, active_tab: TabId, active_ui_tab: UiTabId, @@ -82,11 +104,13 @@ impl Application for ConfiguratorApp { let baseline = defaults.clone(); let override_mode = defaults.ui_toolbar_layout_mode; let config_path = Config::get_config_path().ok(); + let base_config = Arc::new(default_config.clone()); let app = Self { draft: baseline.clone(), baseline, defaults, + base_config, status: StatusMessage::info("Loading configuration..."), active_tab: TabId::Drawing, active_ui_tab: UiTabId::Toolbar, @@ -123,6 +147,7 @@ impl Application for ConfiguratorApp { let draft = ConfigDraft::from_config(config.as_ref()); self.draft = draft.clone(); self.baseline = draft; + self.base_config = config.clone(); self.override_mode = self.draft.ui_toolbar_layout_mode; self.is_dirty = false; self.status = StatusMessage::success("Configuration loaded from disk."); @@ -153,7 +178,7 @@ impl Application for ConfiguratorApp { return Command::none(); } - match self.draft.to_config() { + match self.draft.to_config(self.base_config.as_ref()) { Ok(mut config) => { config.validate_and_clamp(); self.is_saving = true; @@ -180,6 +205,7 @@ impl Application for ConfiguratorApp { self.last_backup_path = backup.clone(); self.draft = draft.clone(); self.baseline = draft; + self.base_config = saved_config.clone(); self.is_dirty = false; let mut msg = "Configuration saved successfully.".to_string(); if let Some(path) = backup { diff --git a/configurator/src/models/config.rs b/configurator/src/models/config.rs index b47b9614..581ec66b 100644 --- a/configurator/src/models/config.rs +++ b/configurator/src/models/config.rs @@ -293,9 +293,9 @@ impl ConfigDraft { } } - pub fn to_config(&self) -> Result> { + pub fn to_config(&self, base: &Config) -> Result> { let mut errors = Vec::new(); - let mut config = Config::default(); + let mut config = base.clone(); match self.drawing_color.to_color_spec() { Ok(color) => config.drawing.default_color = color, @@ -764,7 +764,9 @@ mod tests { selected_named: NamedColorOption::Custom, }; - let errors = draft.to_config().expect_err("expected validation errors"); + let errors = draft + .to_config(&Config::default()) + .expect_err("expected validation errors"); let fields: Vec<&str> = errors.iter().map(|err| err.field.as_str()).collect(); assert!(fields.contains(&"drawing.default_thickness")); @@ -778,7 +780,9 @@ mod tests { draft.session_storage_mode = SessionStorageModeOption::Custom; draft.session_custom_directory = " ".to_string(); - let config = draft.to_config().expect("to_config should succeed"); + let config = draft + .to_config(&Config::default()) + .expect("to_config should succeed"); assert!(config.session.custom_directory.is_none()); } diff --git a/docs/CONFIG.md b/docs/CONFIG.md index ccea88d4..fde7de09 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -437,13 +437,13 @@ exit_after_capture = false ### `[session]` - Session Persistence -Optional on-disk persistence for your drawings. Disabled by default so each session starts fresh. +Optional on-disk persistence for your drawings. Enabled by default so sessions resume automatically. ```toml [session] -persist_transparent = false -persist_whiteboard = false -persist_blackboard = false +persist_transparent = true +persist_whiteboard = true +persist_blackboard = true persist_history = true restore_tool_state = true storage = "auto" diff --git a/src/config/mod.rs b/src/config/mod.rs index 9017f543..54b094fe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -417,7 +417,7 @@ pub(super) fn primary_config_dir() -> Result { /// exit = ["Escape", "Ctrl+Q"] /// undo = ["Ctrl+Z"] /// ``` -#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)] pub struct Config { /// Drawing tool defaults (color, thickness, font size) #[serde(default)] diff --git a/src/config/types.rs b/src/config/types.rs index 409ecaa5..b9ef1345 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -13,7 +13,7 @@ pub const PRESET_SLOTS_MAX: usize = 5; /// /// Controls the default appearance of drawing tools when the overlay first opens. /// Users can change these values at runtime using keybindings. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct DrawingConfig { /// Default pen color - either a named color (red, green, blue, yellow, orange, pink, white, black) /// or an RGB array like `[255, 0, 0]` for red @@ -222,7 +222,7 @@ impl Default for PresetSlotsConfig { /// Arrow drawing settings. /// /// Controls the appearance of arrowheads when using the arrow tool (Ctrl+Shift+Drag). -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct ArrowConfig { /// Arrowhead length in pixels (valid range: 5.0 - 50.0) #[serde(default = "default_arrow_length")] @@ -249,7 +249,7 @@ impl Default for ArrowConfig { } /// Undo/redo playback configuration. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct HistoryConfig { /// Delay in milliseconds between steps when running "undo all by delay" #[serde(default = "default_undo_all_delay_ms")] @@ -298,7 +298,7 @@ impl Default for HistoryConfig { /// /// These settings control rendering performance and smoothness. Most users /// won't need to change these from their defaults. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct PerformanceConfig { /// Number of buffers for buffering (valid range: 2 - 4) /// - 2 = double buffering (lower memory, potential tearing) @@ -331,7 +331,7 @@ impl Default for PerformanceConfig { /// UI display preferences. /// /// Controls the visibility and positioning of on-screen UI elements. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct UiConfig { /// Show the status bar displaying current color, thickness, and tool #[serde(default = "default_show_status")] @@ -394,7 +394,7 @@ impl Default for UiConfig { } /// Status bar styling configuration. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct StatusBarStyle { /// Font size for status bar text #[serde(default = "default_status_font_size")] @@ -430,7 +430,7 @@ impl Default for StatusBarStyle { } /// Click highlight configuration for mouse press indicator. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct ClickHighlightConfig { /// Whether the highlight effect starts enabled #[serde(default = "default_click_highlight_enabled")] @@ -462,7 +462,7 @@ pub struct ClickHighlightConfig { } /// Context menu visibility configuration. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct ContextMenuUiConfig { #[serde(default = "default_context_menu_enabled")] pub enabled: bool, @@ -491,7 +491,7 @@ impl Default for ClickHighlightConfig { } /// Help overlay styling configuration. -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct HelpOverlayStyle { /// Font size for help overlay text #[serde(default = "default_help_font_size")] @@ -541,7 +541,7 @@ impl Default for HelpOverlayStyle { // ───────────────────────────────────────────────────────────────────────────── #[cfg(tablet)] -#[derive(Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct TabletInputConfig { /// Enable tablet/stylus events at runtime (feature must be compiled in). #[serde(default = "default_tablet_enabled")] @@ -1015,9 +1015,9 @@ pub struct SessionConfig { impl Default for SessionConfig { fn default() -> Self { Self { - persist_transparent: false, - persist_whiteboard: false, - persist_blackboard: false, + persist_transparent: true, + persist_whiteboard: true, + persist_blackboard: true, persist_history: default_persist_history(), restore_tool_state: default_restore_tool_state(), storage: default_session_storage_mode(),