Skip to main content

wasma_sys/
wasma_protocol_unix_posix_opt.rs

1// WASMA - Windows Assignment System Monitoring Architecture
2// wasma_protocol_unix_posix_opt.rs
3// POSIX Optionalty Configuration
4// All optional settings for WASMA window drawing, rendering, and backend selection.
5// Groups: DrawOpt, PixelOpt, XlinxOpt, BackendOpt
6// Design: Grouped structs + Builder pattern (both)
7// Source: wasma.in.conf parse + runtime override API
8// January 2026
9
10use crate::parser::WasmaConfig;
11use crate::wasma_protocol_universal_client_unix_posix_window::{ArchKind, CURRENT_ARCH};
12use std::sync::{Arc, RwLock};
13
14// ============================================================================
15// DRAW OPT — Drawing size and toolkit theme selection
16// ============================================================================
17
18/// UI toolkit selection
19#[derive(Debug, Clone, PartialEq)]
20pub enum ToolkitTheme {
21    /// GTK (GNOME toolkit) — GTK3/GTK4
22    Gtk(GtkThemeOpt),
23    /// Iced (Rust-native GUI)
24    Iced(IcedThemeOpt),
25    /// Qt (KDE toolkit) — Qt5/Qt6
26    Qt(QtThemeOpt),
27    /// No toolkit — raw drawing only
28    None,
29}
30
31impl ToolkitTheme {
32    pub fn name(&self) -> &'static str {
33        match self {
34            Self::Gtk(_) => "GTK",
35            Self::Iced(_) => "Iced",
36            Self::Qt(_) => "Qt",
37            Self::None => "None (raw)",
38        }
39    }
40}
41
42/// GTK theme options
43#[derive(Debug, Clone, PartialEq)]
44pub struct GtkThemeOpt {
45    /// Theme name (e.g. "Adwaita", "Arc", "Breeze")
46    pub theme_name: String,
47    /// GTK version (3 or 4)
48    pub version: u8,
49    /// Dark mode
50    pub dark_mode: bool,
51    /// Font scaling factor
52    pub font_scale: f32,
53    /// Icon theme name
54    pub icon_theme: Option<String>,
55}
56
57impl Default for GtkThemeOpt {
58    fn default() -> Self {
59        Self {
60            theme_name: "Adwaita".to_string(),
61            version: 4,
62            dark_mode: false,
63            font_scale: 1.0,
64            icon_theme: None,
65        }
66    }
67}
68
69/// Iced theme options
70#[derive(Debug, Clone, PartialEq)]
71pub struct IcedThemeOpt {
72    /// Built-in theme variant
73    pub variant: IcedThemeVariant,
74    /// Custom accent color (RGBA)
75    pub accent_color: Option<[u8; 4]>,
76    /// Font family
77    pub font_family: Option<String>,
78    /// Default text size in logical pixels
79    pub text_size: f32,
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub enum IcedThemeVariant {
84    Light,
85    Dark,
86    Dracula,
87    Nord,
88    SolarizedLight,
89    SolarizedDark,
90    GruvboxLight,
91    GruvboxDark,
92    CatppuccinLatte,
93    CatppuccinMocha,
94    Custom(String),
95}
96
97impl Default for IcedThemeOpt {
98    fn default() -> Self {
99        Self {
100            variant: IcedThemeVariant::Dark,
101            accent_color: None,
102            font_family: None,
103            text_size: 16.0,
104        }
105    }
106}
107
108/// Qt theme options
109#[derive(Debug, Clone, PartialEq)]
110pub struct QtThemeOpt {
111    /// Style name (e.g. "Fusion", "Breeze", "Oxygen")
112    pub style_name: String,
113    /// Qt version (5 or 6)
114    pub version: u8,
115    /// Dark mode
116    pub dark_mode: bool,
117    /// Platform theme (e.g. "gtk2", "gnome", "kde")
118    pub platform_theme: Option<String>,
119    /// Font point size
120    pub font_pt: f32,
121}
122
123impl Default for QtThemeOpt {
124    fn default() -> Self {
125        Self {
126            style_name: "Fusion".to_string(),
127            version: 6,
128            dark_mode: false,
129            platform_theme: None,
130            font_pt: 10.0,
131        }
132    }
133}
134
135/// Drawing size configuration
136#[derive(Debug, Clone, PartialEq)]
137pub struct DrawSizeOpt {
138    /// Minimum window width in logical pixels
139    pub min_width: u32,
140    /// Minimum window height in logical pixels
141    pub min_height: u32,
142    /// Maximum window width (None = unlimited)
143    pub max_width: Option<u32>,
144    /// Maximum window height (None = unlimited)
145    pub max_height: Option<u32>,
146    /// Default window width
147    pub default_width: u32,
148    /// Default window height
149    pub default_height: u32,
150    /// Aspect ratio constraint (width/height, None = free)
151    pub aspect_ratio: Option<f32>,
152    /// Whether window is resizable
153    pub resizable: bool,
154}
155
156impl Default for DrawSizeOpt {
157    fn default() -> Self {
158        Self {
159            min_width: 100,
160            min_height: 100,
161            max_width: None,
162            max_height: None,
163            default_width: 800,
164            default_height: 600,
165            aspect_ratio: None,
166            resizable: true,
167        }
168    }
169}
170
171/// Drawing options group
172#[derive(Debug, Clone)]
173pub struct DrawOpt {
174    pub size: DrawSizeOpt,
175    pub toolkit: ToolkitTheme,
176    /// Anti-aliasing enabled
177    pub antialiasing: bool,
178    /// Subpixel rendering (ClearType-style)
179    pub subpixel_rendering: bool,
180    /// Vsync
181    pub vsync: bool,
182    /// Target FPS (None = unlimited)
183    pub target_fps: Option<u32>,
184}
185
186impl Default for DrawOpt {
187    fn default() -> Self {
188        Self {
189            size: DrawSizeOpt::default(),
190            toolkit: ToolkitTheme::Iced(IcedThemeOpt::default()),
191            antialiasing: true,
192            subpixel_rendering: false,
193            vsync: true,
194            target_fps: None,
195        }
196    }
197}
198
199// ============================================================================
200// PIXEL OPT — Pixel size, color, and grid configuration
201// ============================================================================
202
203/// Pixel color format
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub enum PixelColorFormat {
206    Rgb888,
207    Rgba8888,
208    Bgr888,
209    Bgra8888,
210    Rgb565,
211    /// 10-bit HDR
212    Rgb101010,
213    Rgba16Float,
214    /// Grayscale
215    Luma8,
216}
217
218impl PixelColorFormat {
219    pub fn bytes_per_pixel(&self) -> u8 {
220        match self {
221            Self::Rgb888 => 3,
222            Self::Rgba8888 => 4,
223            Self::Bgr888 => 3,
224            Self::Bgra8888 => 4,
225            Self::Rgb565 => 2,
226            Self::Rgb101010 => 4,
227            Self::Rgba16Float => 8,
228            Self::Luma8 => 1,
229        }
230    }
231
232    pub fn name(&self) -> &'static str {
233        match self {
234            Self::Rgb888 => "RGB888",
235            Self::Rgba8888 => "RGBA8888",
236            Self::Bgr888 => "BGR888",
237            Self::Bgra8888 => "BGRA8888",
238            Self::Rgb565 => "RGB565",
239            Self::Rgb101010 => "RGB101010",
240            Self::Rgba16Float => "RGBA16F",
241            Self::Luma8 => "Luma8",
242        }
243    }
244}
245
246/// Pixel grid configuration — screen-dependent
247/// Adapts grid parameters to actual screen properties
248#[derive(Debug, Clone, PartialEq)]
249pub struct PixelGridScreenDep {
250    /// Grid cell width tied to screen pixel pitch
251    pub cell_width_px: u32,
252    /// Grid cell height tied to screen pixel pitch
253    pub cell_height_px: u32,
254    /// Grid line color (RGBA)
255    pub line_color: [u8; 4],
256    /// Grid line width in physical pixels
257    pub line_width_phys: f32,
258    /// Show grid (debug/design mode)
259    pub visible: bool,
260    /// Snap windows to grid
261    pub snap_to_grid: bool,
262}
263
264impl Default for PixelGridScreenDep {
265    fn default() -> Self {
266        Self {
267            cell_width_px: 8,
268            cell_height_px: 8,
269            line_color: [200, 200, 200, 64],
270            line_width_phys: 0.5,
271            visible: false,
272            snap_to_grid: false,
273        }
274    }
275}
276
277/// Pixel grid configuration — screen-independent
278/// Fixed logical units, DPI-agnostic
279#[derive(Debug, Clone, PartialEq)]
280pub struct PixelGridScreenIndep {
281    /// Grid cell size in logical pixels (DPI-independent)
282    pub cell_size_logical: f32,
283    /// Sub-grid divisions per cell
284    pub subdivisions: u8,
285    /// Major grid every N cells
286    pub major_every: u32,
287    /// Major line color (RGBA)
288    pub major_color: [u8; 4],
289    /// Minor line color (RGBA)
290    pub minor_color: [u8; 4],
291    pub visible: bool,
292}
293
294impl Default for PixelGridScreenIndep {
295    fn default() -> Self {
296        Self {
297            cell_size_logical: 16.0,
298            subdivisions: 4,
299            major_every: 8,
300            major_color: [150, 150, 150, 128],
301            minor_color: [200, 200, 200, 64],
302            visible: false,
303        }
304    }
305}
306
307/// Pixel size configuration
308#[derive(Debug, Clone, PartialEq)]
309pub struct PixelSizeOpt {
310    /// Logical pixel size multiplier (1.0 = normal, 2.0 = 2x zoom)
311    pub logical_scale: f32,
312    /// Physical pixel size in micrometers (0 = auto from DPI)
313    pub physical_size_um: f32,
314    /// Minimum renderable pixel size in logical units
315    pub min_size_logical: f32,
316}
317
318impl Default for PixelSizeOpt {
319    fn default() -> Self {
320        Self {
321            logical_scale: 1.0,
322            physical_size_um: 0.0,
323            min_size_logical: 0.5,
324        }
325    }
326}
327
328/// Pixel options group
329#[derive(Debug, Clone)]
330pub struct PixelOpt {
331    pub size: PixelSizeOpt,
332    pub color_format: PixelColorFormat,
333    /// Background color (RGBA)
334    pub background_color: [u8; 4],
335    /// Clear color (RGBA) — used when clearing framebuffer
336    pub clear_color: [u8; 4],
337    pub grid_screen_dep: PixelGridScreenDep,
338    pub grid_screen_indep: PixelGridScreenIndep,
339    /// Enable pixel-perfect rendering (snap to integer coordinates)
340    pub pixel_perfect: bool,
341    /// Gamma correction value (1.0 = linear, 2.2 = sRGB)
342    pub gamma: f32,
343}
344
345impl Default for PixelOpt {
346    fn default() -> Self {
347        Self {
348            size: PixelSizeOpt::default(),
349            color_format: PixelColorFormat::Rgba8888,
350            background_color: [30, 30, 30, 255],
351            clear_color: [0, 0, 0, 255],
352            grid_screen_dep: PixelGridScreenDep::default(),
353            grid_screen_indep: PixelGridScreenIndep::default(),
354            pixel_perfect: true,
355            gamma: 2.2,
356        }
357    }
358}
359
360// ============================================================================
361// XLINX OPT — Cache, rasterization, architecture, software fallback
362// ============================================================================
363
364/// Xlinx cache mode
365#[derive(Debug, Clone, Copy, PartialEq)]
366pub enum XlinxCacheMode {
367    /// Automatic cache management (default)
368    Auto,
369    /// No cache — always redraw (debug/profiling)
370    NoCache,
371    /// Manual cache control via API
372    Manual,
373    /// Aggressive caching — cache everything possible
374    Aggressive,
375}
376
377impl XlinxCacheMode {
378    pub fn name(&self) -> &'static str {
379        match self {
380            Self::Auto => "auto",
381            Self::NoCache => "no_cache",
382            Self::Manual => "manual",
383            Self::Aggressive => "aggressive",
384        }
385    }
386
387    pub fn from_str(s: &str) -> Self {
388        match s {
389            "no_cache" | "none" => Self::NoCache,
390            "manual" => Self::Manual,
391            "aggressive" | "full" => Self::Aggressive,
392            _ => Self::Auto,
393        }
394    }
395}
396
397/// Rasterization mode
398#[derive(Debug, Clone, Copy, PartialEq)]
399pub enum RasterMode {
400    /// Debug rasterization — slow, annotated output (only in debug builds)
401    /// WARNING: not for production use
402    Debug,
403    /// Hardware-based rasterization (GPU, requires hardware support)
404    Hardware,
405    /// Software-based rasterization (CPU fallback via Xlinx)
406    Software,
407    /// Auto: hardware if available, software fallback
408    Auto,
409}
410
411impl RasterMode {
412    pub fn name(&self) -> &'static str {
413        match self {
414            Self::Debug => "debug (rasterization)",
415            Self::Hardware => "hardware",
416            Self::Software => "software (xlinx fallback)",
417            Self::Auto => "auto",
418        }
419    }
420
421    pub fn from_str(s: &str) -> Self {
422        match s {
423            "debug" | "dbg" => Self::Debug,
424            "hardware" | "hw" => Self::Hardware,
425            "software" | "sw" => Self::Software,
426            _ => Self::Auto,
427        }
428    }
429
430    pub fn is_debug_only(&self) -> bool {
431        matches!(self, Self::Debug)
432    }
433}
434
435/// Xlinx base architecture selection
436/// Determines which Xlinx rendering pipeline variant is used
437#[derive(Debug, Clone, Copy, PartialEq)]
438pub enum XlinxArch {
439    /// x86_64 native pipeline
440    X86_64,
441    /// AArch64 / ARM64 native pipeline
442    Aarch64,
443    /// RISC-V pipeline
444    RiscV,
445    /// Generic / portable pipeline (for unsupported archs)
446    Generic,
447    /// Software-only pipeline (Xlinx-based software rasterization)
448    SoftwareOnly,
449    /// Auto-detect from compile-time arch
450    Auto,
451}
452
453impl XlinxArch {
454    pub fn auto_detect() -> Self {
455        match CURRENT_ARCH {
456            ArchKind::Amd64 => Self::X86_64,
457            ArchKind::Aarch64 => Self::Aarch64,
458            ArchKind::RiscV64 => Self::RiscV,
459            ArchKind::Sisd | ArchKind::Unknown => Self::SoftwareOnly,
460            _ => Self::Generic,
461        }
462    }
463
464    pub fn name(&self) -> &'static str {
465        match self {
466            Self::X86_64 => "x86_64",
467            Self::Aarch64 => "aarch64",
468            Self::RiscV => "riscv",
469            Self::Generic => "generic",
470            Self::SoftwareOnly => "software_only",
471            Self::Auto => "auto",
472        }
473    }
474
475    pub fn from_str(s: &str) -> Self {
476        match s {
477            "x86_64" | "amd64" => Self::X86_64,
478            "aarch64" | "arm64" => Self::Aarch64,
479            "riscv" | "riscv64" => Self::RiscV,
480            "generic" => Self::Generic,
481            "software" | "sw" => Self::SoftwareOnly,
482            _ => Self::Auto,
483        }
484    }
485}
486
487/// USET (User Environment SET) permission flags
488/// Controls which Xlinx features the user environment is allowed to configure
489#[derive(Debug, Clone, PartialEq)]
490pub struct XlinxUsetPermission {
491    /// User can change cache mode
492    pub allow_cache_change: bool,
493    /// User can switch raster mode (cannot enable Debug in production)
494    pub allow_raster_change: bool,
495    /// User can change arch selection
496    pub allow_arch_change: bool,
497    /// User can disable hardware acceleration
498    pub allow_sw_fallback: bool,
499    /// User can modify auto-log settings
500    pub allow_log_config: bool,
501}
502
503impl Default for XlinxUsetPermission {
504    fn default() -> Self {
505        Self {
506            allow_cache_change: true,
507            allow_raster_change: true,
508            allow_arch_change: false, // arch change is system-level
509            allow_sw_fallback: true,
510            allow_log_config: true,
511        }
512    }
513}
514
515/// Xlinx auto-log configuration
516#[derive(Debug, Clone, PartialEq)]
517pub struct XlinxAutoLog {
518    /// Enable automatic logging of Xlinx events
519    pub enabled: bool,
520    /// Log level: 0=off, 1=errors, 2=warnings, 3=info, 4=debug, 5=trace
521    pub level: u8,
522    /// Log output path (None = stderr)
523    pub output_path: Option<String>,
524    /// Log frame timing
525    pub log_frame_timing: bool,
526    /// Log cache hit/miss ratio
527    pub log_cache_stats: bool,
528    /// Log raster operations
529    pub log_raster_ops: bool,
530}
531
532impl Default for XlinxAutoLog {
533    fn default() -> Self {
534        Self {
535            enabled: false,
536            level: 2,
537            output_path: None,
538            log_frame_timing: false,
539            log_cache_stats: false,
540            log_raster_ops: false,
541        }
542    }
543}
544
545/// Xlinx software rasterization fallback
546/// Used when hardware rasterization is unavailable or for unsupported archs
547#[derive(Debug, Clone, PartialEq)]
548pub struct XlinxSoftFallback {
549    /// Enable software fallback
550    pub enabled: bool,
551    /// Number of software rasterizer threads
552    pub thread_count: u8,
553    /// Tile size for parallel software rasterization
554    pub tile_width: u32,
555    pub tile_height: u32,
556    /// Enable MSAA in software mode
557    pub software_msaa: bool,
558    /// MSAA sample count (1, 2, 4, 8)
559    pub msaa_samples: u8,
560}
561
562impl Default for XlinxSoftFallback {
563    fn default() -> Self {
564        Self {
565            enabled: true,
566            thread_count: 4,
567            tile_width: 64,
568            tile_height: 64,
569            software_msaa: false,
570            msaa_samples: 1,
571        }
572    }
573}
574
575/// Xlinx options group
576#[derive(Debug, Clone)]
577pub struct XlinxOpt {
578    pub cache_mode: XlinxCacheMode,
579    pub raster_mode: RasterMode,
580    pub arch: XlinxArch,
581    pub soft_fallback: XlinxSoftFallback,
582    pub auto_log: XlinxAutoLog,
583    pub uset_permission: XlinxUsetPermission,
584    /// Hardware rasterization support flag
585    /// Automatically set based on arch + driver availability
586    pub hw_raster_supported: bool,
587    /// Xlinx pipeline version (internal)
588    pub pipeline_version: u8,
589}
590
591impl Default for XlinxOpt {
592    fn default() -> Self {
593        let arch = XlinxArch::auto_detect();
594        let hw_supported = !matches!(arch, XlinxArch::SoftwareOnly | XlinxArch::Generic);
595        Self {
596            cache_mode: XlinxCacheMode::Auto,
597            raster_mode: RasterMode::Auto,
598            arch,
599            soft_fallback: XlinxSoftFallback::default(),
600            auto_log: XlinxAutoLog::default(),
601            uset_permission: XlinxUsetPermission::default(),
602            hw_raster_supported: hw_supported,
603            pipeline_version: 1,
604        }
605    }
606}
607
608impl XlinxOpt {
609    /// Effective raster mode — resolves Auto to concrete mode
610    pub fn effective_raster_mode(&self) -> RasterMode {
611        match self.raster_mode {
612            RasterMode::Auto => {
613                if self.hw_raster_supported {
614                    RasterMode::Hardware
615                } else {
616                    RasterMode::Software
617                }
618            }
619            other => other,
620        }
621    }
622
623    /// Is debug rasterization active?
624    /// NOTE: only meaningful in debug builds
625    pub fn is_debug_raster(&self) -> bool {
626        cfg!(debug_assertions) && matches!(self.raster_mode, RasterMode::Debug)
627    }
628
629    /// Apply USET permission check before a change
630    pub fn check_uset(&self, change: &str) -> Result<(), String> {
631        match change {
632            "cache" if !self.uset_permission.allow_cache_change => {
633                Err("USET: cache change not permitted".to_string())
634            }
635            "raster" if !self.uset_permission.allow_raster_change => {
636                Err("USET: raster mode change not permitted".to_string())
637            }
638            "arch" if !self.uset_permission.allow_arch_change => {
639                Err("USET: arch change not permitted".to_string())
640            }
641            "sw_fallback" if !self.uset_permission.allow_sw_fallback => {
642                Err("USET: software fallback change not permitted".to_string())
643            }
644            "log" if !self.uset_permission.allow_log_config => {
645                Err("USET: log config change not permitted".to_string())
646            }
647            _ => Ok(()),
648        }
649    }
650}
651
652// ============================================================================
653// BACKEND OPT — X11 / Wayland / Xwayland selection
654// ============================================================================
655
656/// Display backend selection
657#[derive(Debug, Clone, PartialEq)]
658pub enum DisplayBackend {
659    /// X11 / Xorg (XCB-based)
660    X11,
661    /// Wayland (native surfacing)
662    Wayland,
663    /// XWayland — X11 over Wayland (debug only)
664    /// WARNING: only use when X11 backend or X11 is active
665    XWayland,
666    /// Auto-detect: prefer Wayland, fallback to X11
667    Auto,
668    /// Headless (no display, for testing/offscreen)
669    Headless,
670}
671
672impl DisplayBackend {
673    pub fn name(&self) -> &'static str {
674        match self {
675            Self::X11 => "X11 (Xorg/XCB)",
676            Self::Wayland => "Wayland",
677            Self::XWayland => "XWayland (X11 over Wayland, debug)",
678            Self::Auto => "Auto (Wayland preferred)",
679            Self::Headless => "Headless",
680        }
681    }
682
683    pub fn from_str(s: &str) -> Self {
684        match s {
685            "x11" | "xorg" | "xcb" => Self::X11,
686            "wayland" | "wl" => Self::Wayland,
687            "xwayland" => Self::XWayland,
688            "headless" => Self::Headless,
689            _ => Self::Auto,
690        }
691    }
692
693    pub fn is_xwayland(&self) -> bool {
694        matches!(self, Self::XWayland)
695    }
696}
697
698/// X11 / Xorg debug and management settings
699/// NOTE: These settings are disabled if X11 backend is not active
700#[derive(Debug, Clone, PartialEq)]
701pub struct X11DebugOpt {
702    /// Enable X11 XCB debug output
703    pub xcb_debug: bool,
704    /// Log X11 protocol events
705    pub log_events: bool,
706    /// Synchronous mode (slow, for debugging)
707    pub sync_mode: bool,
708    /// Window border width (debug visual aid)
709    pub debug_border_width: u8,
710    /// Show window decorations
711    pub decorations: bool,
712    /// X11 composite manager interaction
713    pub use_composite: bool,
714    /// Override redirect (bypass WM)
715    pub override_redirect: bool,
716    /// X11 backing store (NotUseful / WhenMapped / Always)
717    pub backing_store: X11BackingStore,
718}
719
720#[derive(Debug, Clone, Copy, PartialEq)]
721pub enum X11BackingStore {
722    NotUseful,
723    WhenMapped,
724    Always,
725}
726
727impl Default for X11DebugOpt {
728    fn default() -> Self {
729        Self {
730            xcb_debug: false,
731            log_events: false,
732            sync_mode: false,
733            debug_border_width: 0,
734            decorations: true,
735            use_composite: true,
736            override_redirect: false,
737            backing_store: X11BackingStore::NotUseful,
738        }
739    }
740}
741
742/// Wayland surfacing support options
743/// Includes both drawing and window output with all settings
744#[derive(Debug, Clone, PartialEq)]
745pub struct WaylandSurfaceOpt {
746    /// Enable Wayland subsurface support
747    pub subsurface_support: bool,
748    /// Buffer format preference
749    pub preferred_format: PixelColorFormat,
750    /// Enable fractional scaling (wl_fractional_scale_v1)
751    pub fractional_scale: bool,
752    /// Enable xdg-decoration protocol
753    pub server_side_decorations: bool,
754    /// Enable layer shell (for panels/overlays)
755    pub layer_shell: bool,
756    /// Enable presentation-time protocol
757    pub presentation_time: bool,
758    /// Enable explicit synchronization
759    pub explicit_sync: bool,
760    /// DMA-BUF import support
761    pub dmabuf: bool,
762    /// Output scale hint (1 = 1x, 2 = HiDPI 2x)
763    pub output_scale: u32,
764    /// Force use of shared memory (shm) buffers
765    pub force_shm: bool,
766}
767
768impl Default for WaylandSurfaceOpt {
769    fn default() -> Self {
770        Self {
771            subsurface_support: true,
772            preferred_format: PixelColorFormat::Rgba8888,
773            fractional_scale: true,
774            server_side_decorations: true,
775            layer_shell: false,
776            presentation_time: true,
777            explicit_sync: false,
778            dmabuf: true,
779            output_scale: 1,
780            force_shm: false,
781        }
782    }
783}
784
785/// X11 supported change-to-Wayland (ava x wayland) settings
786/// Controls automatic backend switching behavior
787#[derive(Debug, Clone, PartialEq)]
788pub struct X11ToWaylandOpt {
789    /// Enable automatic switch from X11 to Wayland when available
790    pub auto_switch: bool,
791    /// Minimum Wayland compositor version required to switch
792    pub min_compositor_version: u32,
793    /// Fallback to X11 if Wayland init fails
794    pub fallback_to_x11: bool,
795    /// Prefer XWayland for specific app IDs
796    pub xwayland_app_ids: Vec<String>,
797}
798
799impl Default for X11ToWaylandOpt {
800    fn default() -> Self {
801        Self {
802            auto_switch: true,
803            min_compositor_version: 1,
804            fallback_to_x11: true,
805            xwayland_app_ids: Vec::new(),
806        }
807    }
808}
809
810/// Backend options group
811#[derive(Debug, Clone)]
812pub struct BackendOpt {
813    pub backend: DisplayBackend,
814    pub x11: X11DebugOpt,
815    pub wayland: WaylandSurfaceOpt,
816    pub x11_to_wayland: X11ToWaylandOpt,
817    /// Whether X11 is currently active (affects x11/xwayland settings)
818    pub x11_active: bool,
819    /// Whether Wayland compositor is available
820    pub wayland_available: bool,
821}
822
823impl Default for BackendOpt {
824    fn default() -> Self {
825        // Auto-detect: check WAYLAND_DISPLAY env var
826        let wayland_available = std::env::var("WAYLAND_DISPLAY").is_ok();
827        let x11_active = std::env::var("DISPLAY").is_ok();
828
829        Self {
830            backend: DisplayBackend::Auto,
831            x11: X11DebugOpt::default(),
832            wayland: WaylandSurfaceOpt::default(),
833            x11_to_wayland: X11ToWaylandOpt::default(),
834            x11_active,
835            wayland_available,
836        }
837    }
838}
839
840impl BackendOpt {
841    /// Resolve effective backend — Auto → concrete selection
842    pub fn effective_backend(&self) -> DisplayBackend {
843        match &self.backend {
844            DisplayBackend::Auto => {
845                if self.wayland_available {
846                    DisplayBackend::Wayland
847                } else if self.x11_active {
848                    DisplayBackend::X11
849                } else {
850                    DisplayBackend::Headless
851                }
852            }
853            other => other.clone(),
854        }
855    }
856
857    /// Are X11 settings active?
858    /// NOTE: if X11 backend or X11 is not active, X11 settings are disabled
859    pub fn x11_settings_active(&self) -> bool {
860        self.x11_active
861            && matches!(
862                self.effective_backend(),
863                DisplayBackend::X11 | DisplayBackend::XWayland
864            )
865    }
866
867    /// Are Wayland settings active?
868    pub fn wayland_settings_active(&self) -> bool {
869        self.wayland_available
870            && matches!(
871                self.effective_backend(),
872                DisplayBackend::Wayland | DisplayBackend::XWayland
873            )
874    }
875
876    /// Validate XWayland usage
877    /// XWayland is only valid when X11 backend or X11 is active
878    pub fn validate_xwayland(&self) -> Result<(), String> {
879        if matches!(self.backend, DisplayBackend::XWayland) {
880            if !self.x11_active {
881                return Err(
882                    "XWayland requires X11 backend to be active (DISPLAY not set)".to_string(),
883                );
884            }
885        }
886        Ok(())
887    }
888}
889
890// ============================================================================
891// POSIX OPT — Master configuration struct
892// ============================================================================
893
894/// PosixOpt — master optionalty configuration
895/// Combines all option groups with runtime override support
896#[derive(Debug, Clone)]
897pub struct PosixOpt {
898    pub draw: DrawOpt,
899    pub pixel: PixelOpt,
900    pub xlinx: XlinxOpt,
901    pub backend: BackendOpt,
902    /// Config source label (for diagnostics)
903    pub source: String,
904}
905
906impl Default for PosixOpt {
907    fn default() -> Self {
908        Self {
909            draw: DrawOpt::default(),
910            pixel: PixelOpt::default(),
911            xlinx: XlinxOpt::default(),
912            backend: BackendOpt::default(),
913            source: "default".to_string(),
914        }
915    }
916}
917
918impl PosixOpt {
919    /// Parse from WasmaConfig (config file values)
920    pub fn from_config(config: &WasmaConfig) -> Self {
921        let mut opt = Self::default();
922        opt.source = "wasma.in.conf".to_string();
923
924        // Derive draw size from config
925        opt.draw.size.default_width = 800;
926        opt.draw.size.default_height = 600;
927
928        // Scope level → xlinx arch selection
929        opt.xlinx.arch = XlinxArch::auto_detect();
930
931        // Renderer → raster mode
932        opt.xlinx.raster_mode = match config.resource_limits.renderer.as_str() {
933            "glx_renderer" | "opencl" | "renderer_opencl" => RasterMode::Hardware,
934            "cpu_renderer" | "cpu" => RasterMode::Software,
935            _ => RasterMode::Auto,
936        };
937
938        // scope_level == 0 → no cache (NULL_EXCEPTION mode)
939        if config.resource_limits.scope_level == 0 {
940            opt.xlinx.cache_mode = XlinxCacheMode::NoCache;
941        }
942
943        // execution_mode → toolkit hint
944        opt.draw.toolkit = ToolkitTheme::Iced(IcedThemeOpt::default());
945
946        // Backend: singularity → headless hint
947        if config.uri_handling.singularity_instances {
948            opt.draw.vsync = true;
949        }
950
951        opt
952    }
953
954    /// Validate all option groups
955    pub fn validate(&self) -> Result<(), Vec<String>> {
956        let mut errors = Vec::new();
957
958        // Validate XWayland
959        if let Err(e) = self.backend.validate_xwayland() {
960            errors.push(e);
961        }
962
963        // Debug raster only in debug builds
964        if matches!(self.xlinx.raster_mode, RasterMode::Debug) {
965            #[cfg(not(debug_assertions))]
966            errors.push("RasterMode::Debug is only allowed in debug builds".to_string());
967        }
968
969        // MSAA samples must be power of 2
970        let s = self.xlinx.soft_fallback.msaa_samples;
971        if s != 1 && s != 2 && s != 4 && s != 8 {
972            errors.push(format!(
973                "Invalid MSAA samples: {} (must be 1, 2, 4, or 8)",
974                s
975            ));
976        }
977
978        // Draw size sanity
979        if self.draw.size.min_width > self.draw.size.default_width {
980            errors.push("min_width > default_width".to_string());
981        }
982        if self.draw.size.min_height > self.draw.size.default_height {
983            errors.push("min_height > default_height".to_string());
984        }
985
986        // Gamma range
987        if self.pixel.gamma < 0.1 || self.pixel.gamma > 10.0 {
988            errors.push(format!(
989                "Gamma out of range: {} (expected 0.1–10.0)",
990                self.pixel.gamma
991            ));
992        }
993
994        if errors.is_empty() {
995            Ok(())
996        } else {
997            Err(errors)
998        }
999    }
1000
1001    /// Print diagnostic summary
1002    pub fn print_summary(&self) {
1003        println!("╔═══════════════════════════════════════════════════════════╗");
1004        println!("║              WASMA PosixOpt Summary                       ║");
1005        println!("╚═══════════════════════════════════════════════════════════╝");
1006        println!("Source: {}", self.source);
1007        println!("\n[DrawOpt]");
1008        println!("  Toolkit:     {}", self.draw.toolkit.name());
1009        println!(
1010            "  Size:        {}x{} (min {}x{})",
1011            self.draw.size.default_width,
1012            self.draw.size.default_height,
1013            self.draw.size.min_width,
1014            self.draw.size.min_height
1015        );
1016        println!(
1017            "  Antialiasing: {} | VSync: {} | FPS: {:?}",
1018            self.draw.antialiasing, self.draw.vsync, self.draw.target_fps
1019        );
1020        println!("\n[PixelOpt]");
1021        println!(
1022            "  Color format: {} ({} bpp)",
1023            self.pixel.color_format.name(),
1024            self.pixel.color_format.bytes_per_pixel()
1025        );
1026        println!(
1027            "  Gamma: {} | Pixel-perfect: {}",
1028            self.pixel.gamma, self.pixel.pixel_perfect
1029        );
1030        println!(
1031            "  Grid (screen-dep): visible={} snap={}",
1032            self.pixel.grid_screen_dep.visible, self.pixel.grid_screen_dep.snap_to_grid
1033        );
1034        println!(
1035            "  Grid (screen-indep): visible={} cell={}lpx",
1036            self.pixel.grid_screen_indep.visible, self.pixel.grid_screen_indep.cell_size_logical
1037        );
1038        println!("\n[XlinxOpt]");
1039        println!("  Arch:         {}", self.xlinx.arch.name());
1040        println!("  Cache:        {}", self.xlinx.cache_mode.name());
1041        println!(
1042            "  Raster:       {} (effective: {})",
1043            self.xlinx.raster_mode.name(),
1044            self.xlinx.effective_raster_mode().name()
1045        );
1046        println!("  HW raster:    {}", self.xlinx.hw_raster_supported);
1047        println!(
1048            "  SW fallback:  {} ({} threads, {}x{} tiles)",
1049            self.xlinx.soft_fallback.enabled,
1050            self.xlinx.soft_fallback.thread_count,
1051            self.xlinx.soft_fallback.tile_width,
1052            self.xlinx.soft_fallback.tile_height
1053        );
1054        println!(
1055            "  Auto-log:     {} (level {})",
1056            self.xlinx.auto_log.enabled, self.xlinx.auto_log.level
1057        );
1058        println!("\n[BackendOpt]");
1059        println!(
1060            "  Backend:      {} → effective: {}",
1061            self.backend.backend.name(),
1062            self.backend.effective_backend().name()
1063        );
1064        println!(
1065            "  X11 active:      {} | Wayland available: {}",
1066            self.backend.x11_active, self.backend.wayland_available
1067        );
1068        println!("  X11 settings:    {}", self.backend.x11_settings_active());
1069        println!(
1070            "  Wayland surface: {}",
1071            self.backend.wayland_settings_active()
1072        );
1073        println!(
1074            "  DMA-BUF:      {} | Frac.scale: {}",
1075            self.backend.wayland.dmabuf, self.backend.wayland.fractional_scale
1076        );
1077    }
1078}
1079
1080// ============================================================================
1081// RUNTIME OPT STORE — Thread-safe runtime override
1082// ============================================================================
1083
1084/// Thread-safe runtime option store
1085/// Wraps PosixOpt in RwLock for concurrent read / exclusive write
1086pub struct RuntimeOptStore {
1087    inner: Arc<RwLock<PosixOpt>>,
1088}
1089
1090impl RuntimeOptStore {
1091    pub fn new(base: PosixOpt) -> Self {
1092        Self {
1093            inner: Arc::new(RwLock::new(base)),
1094        }
1095    }
1096
1097    /// Read current options (shared, non-blocking)
1098    pub fn read(&self) -> std::sync::RwLockReadGuard<'_, PosixOpt> {
1099        self.inner.read().unwrap()
1100    }
1101
1102    /// Apply a runtime override via closure
1103    pub fn override_with<F>(&self, f: F)
1104    where
1105        F: FnOnce(&mut PosixOpt),
1106    {
1107        let mut opt = self.inner.write().unwrap();
1108        f(&mut opt);
1109        opt.source = format!("{} + runtime", opt.source);
1110    }
1111
1112    /// Override toolkit theme at runtime
1113    pub fn set_toolkit(&self, toolkit: ToolkitTheme) {
1114        self.override_with(|o| o.draw.toolkit = toolkit);
1115    }
1116
1117    /// Override display backend at runtime
1118    pub fn set_backend(&self, backend: DisplayBackend) {
1119        self.override_with(|o| o.backend.backend = backend);
1120    }
1121
1122    /// Override xlinx cache mode at runtime (USET checked)
1123    pub fn set_cache_mode(&self, mode: XlinxCacheMode) -> Result<(), String> {
1124        let check = self.read().xlinx.check_uset("cache")?;
1125        let _ = check;
1126        self.override_with(|o| o.xlinx.cache_mode = mode);
1127        Ok(())
1128    }
1129
1130    /// Override raster mode at runtime (USET checked)
1131    pub fn set_raster_mode(&self, mode: RasterMode) -> Result<(), String> {
1132        // Block Debug mode in production builds
1133        #[cfg(not(debug_assertions))]
1134        if matches!(mode, RasterMode::Debug) {
1135            return Err("RasterMode::Debug not allowed in release builds".to_string());
1136        }
1137        self.read().xlinx.check_uset("raster")?;
1138        self.override_with(|o| o.xlinx.raster_mode = mode);
1139        Ok(())
1140    }
1141
1142    /// Override color format at runtime
1143    pub fn set_color_format(&self, fmt: PixelColorFormat) {
1144        self.override_with(|o| o.pixel.color_format = fmt);
1145    }
1146
1147    /// Enable/disable screen-dep grid
1148    pub fn set_screen_dep_grid(&self, visible: bool) {
1149        self.override_with(|o| o.pixel.grid_screen_dep.visible = visible);
1150    }
1151
1152    /// Enable/disable screen-indep grid
1153    pub fn set_screen_indep_grid(&self, visible: bool) {
1154        self.override_with(|o| o.pixel.grid_screen_indep.visible = visible);
1155    }
1156
1157    /// Enable/disable auto-log
1158    pub fn set_auto_log(&self, enabled: bool, level: u8) -> Result<(), String> {
1159        self.read().xlinx.check_uset("log")?;
1160        self.override_with(|o| {
1161            o.xlinx.auto_log.enabled = enabled;
1162            o.xlinx.auto_log.level = level;
1163        });
1164        Ok(())
1165    }
1166
1167    pub fn clone_store(&self) -> Self {
1168        Self {
1169            inner: self.inner.clone(),
1170        }
1171    }
1172}
1173
1174// ============================================================================
1175// POSIX OPT BUILDER — Chainable builder pattern
1176// ============================================================================
1177
1178pub struct PosixOptBuilder {
1179    opt: PosixOpt,
1180}
1181
1182impl PosixOptBuilder {
1183    pub fn new() -> Self {
1184        Self {
1185            opt: PosixOpt::default(),
1186        }
1187    }
1188
1189    pub fn from_config(config: &WasmaConfig) -> Self {
1190        Self {
1191            opt: PosixOpt::from_config(config),
1192        }
1193    }
1194
1195    // ---- DrawOpt ----
1196
1197    pub fn toolkit(mut self, toolkit: ToolkitTheme) -> Self {
1198        self.opt.draw.toolkit = toolkit;
1199        self
1200    }
1201
1202    pub fn default_size(mut self, width: u32, height: u32) -> Self {
1203        self.opt.draw.size.default_width = width;
1204        self.opt.draw.size.default_height = height;
1205        self
1206    }
1207
1208    pub fn min_size(mut self, width: u32, height: u32) -> Self {
1209        self.opt.draw.size.min_width = width;
1210        self.opt.draw.size.min_height = height;
1211        self
1212    }
1213
1214    pub fn max_size(mut self, width: u32, height: u32) -> Self {
1215        self.opt.draw.size.max_width = Some(width);
1216        self.opt.draw.size.max_height = Some(height);
1217        self
1218    }
1219
1220    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
1221        self.opt.draw.size.aspect_ratio = Some(ratio);
1222        self
1223    }
1224
1225    pub fn antialiasing(mut self, enabled: bool) -> Self {
1226        self.opt.draw.antialiasing = enabled;
1227        self
1228    }
1229
1230    pub fn vsync(mut self, enabled: bool) -> Self {
1231        self.opt.draw.vsync = enabled;
1232        self
1233    }
1234
1235    pub fn target_fps(mut self, fps: u32) -> Self {
1236        self.opt.draw.target_fps = Some(fps);
1237        self
1238    }
1239
1240    // ---- PixelOpt ----
1241
1242    pub fn color_format(mut self, fmt: PixelColorFormat) -> Self {
1243        self.opt.pixel.color_format = fmt;
1244        self
1245    }
1246
1247    pub fn background_color(mut self, rgba: [u8; 4]) -> Self {
1248        self.opt.pixel.background_color = rgba;
1249        self
1250    }
1251
1252    pub fn gamma(mut self, gamma: f32) -> Self {
1253        self.opt.pixel.gamma = gamma;
1254        self
1255    }
1256
1257    pub fn pixel_perfect(mut self, enabled: bool) -> Self {
1258        self.opt.pixel.pixel_perfect = enabled;
1259        self
1260    }
1261
1262    pub fn screen_dep_grid(mut self, visible: bool, snap: bool) -> Self {
1263        self.opt.pixel.grid_screen_dep.visible = visible;
1264        self.opt.pixel.grid_screen_dep.snap_to_grid = snap;
1265        self
1266    }
1267
1268    pub fn screen_indep_grid(mut self, visible: bool, cell_size: f32) -> Self {
1269        self.opt.pixel.grid_screen_indep.visible = visible;
1270        self.opt.pixel.grid_screen_indep.cell_size_logical = cell_size;
1271        self
1272    }
1273
1274    // ---- XlinxOpt ----
1275
1276    pub fn xlinx_cache(mut self, mode: XlinxCacheMode) -> Self {
1277        self.opt.xlinx.cache_mode = mode;
1278        self
1279    }
1280
1281    pub fn xlinx_raster(mut self, mode: RasterMode) -> Self {
1282        self.opt.xlinx.raster_mode = mode;
1283        self
1284    }
1285
1286    pub fn xlinx_arch(mut self, arch: XlinxArch) -> Self {
1287        self.opt.xlinx.arch = arch;
1288        self.opt.xlinx.hw_raster_supported =
1289            !matches!(arch, XlinxArch::SoftwareOnly | XlinxArch::Generic);
1290        self
1291    }
1292
1293    pub fn xlinx_soft_fallback(mut self, enabled: bool, threads: u8) -> Self {
1294        self.opt.xlinx.soft_fallback.enabled = enabled;
1295        self.opt.xlinx.soft_fallback.thread_count = threads;
1296        self
1297    }
1298
1299    pub fn xlinx_auto_log(mut self, enabled: bool, level: u8) -> Self {
1300        self.opt.xlinx.auto_log.enabled = enabled;
1301        self.opt.xlinx.auto_log.level = level;
1302        self
1303    }
1304
1305    pub fn xlinx_uset(mut self, perm: XlinxUsetPermission) -> Self {
1306        self.opt.xlinx.uset_permission = perm;
1307        self
1308    }
1309
1310    // ---- BackendOpt ----
1311
1312    pub fn backend(mut self, backend: DisplayBackend) -> Self {
1313        self.opt.backend.backend = backend;
1314        self
1315    }
1316
1317    pub fn x11_debug(mut self, xcb_debug: bool, sync_mode: bool) -> Self {
1318        self.opt.backend.x11.xcb_debug = xcb_debug;
1319        self.opt.backend.x11.sync_mode = sync_mode;
1320        self
1321    }
1322
1323    pub fn wayland_surface(mut self, dmabuf: bool, frac_scale: bool) -> Self {
1324        self.opt.backend.wayland.dmabuf = dmabuf;
1325        self.opt.backend.wayland.fractional_scale = frac_scale;
1326        self
1327    }
1328
1329    pub fn x11_to_wayland(mut self, auto_switch: bool, fallback: bool) -> Self {
1330        self.opt.backend.x11_to_wayland.auto_switch = auto_switch;
1331        self.opt.backend.x11_to_wayland.fallback_to_x11 = fallback;
1332        self
1333    }
1334
1335    /// Build and validate
1336    pub fn build(self) -> Result<PosixOpt, Vec<String>> {
1337        self.opt.validate()?;
1338        Ok(self.opt)
1339    }
1340
1341    /// Build without validation
1342    pub fn build_unchecked(self) -> PosixOpt {
1343        self.opt
1344    }
1345}
1346
1347impl Default for PosixOptBuilder {
1348    fn default() -> Self {
1349        Self::new()
1350    }
1351}
1352
1353// ============================================================================
1354// TESTS
1355// ============================================================================
1356
1357#[cfg(test)]
1358mod tests {
1359    use super::*;
1360    use crate::parser::ConfigParser;
1361
1362    fn make_config() -> WasmaConfig {
1363        let parser = ConfigParser::new(None);
1364        let content = parser.generate_default_config();
1365        parser.parse(&content).unwrap()
1366    }
1367
1368    #[test]
1369    fn test_default_posix_opt() {
1370        let opt = PosixOpt::default();
1371        assert!(opt.validate().is_ok());
1372        println!("✅ Default PosixOpt valid");
1373    }
1374
1375    #[test]
1376    fn test_from_config() {
1377        let config = make_config();
1378        let opt = PosixOpt::from_config(&config);
1379        assert_eq!(opt.source, "wasma.in.conf");
1380        assert!(opt.validate().is_ok());
1381        println!(
1382            "✅ PosixOpt::from_config working, raster: {}",
1383            opt.xlinx.raster_mode.name()
1384        );
1385    }
1386
1387    #[test]
1388    fn test_builder_basic() {
1389        let opt = PosixOptBuilder::new()
1390            .default_size(1920, 1080)
1391            .min_size(320, 240)
1392            .antialiasing(true)
1393            .vsync(false)
1394            .color_format(PixelColorFormat::Rgba8888)
1395            .gamma(2.2)
1396            .xlinx_cache(XlinxCacheMode::Auto)
1397            .xlinx_raster(RasterMode::Hardware)
1398            .backend(DisplayBackend::Wayland)
1399            .build()
1400            .unwrap();
1401
1402        assert_eq!(opt.draw.size.default_width, 1920);
1403        assert_eq!(opt.draw.size.default_height, 1080);
1404        assert!(!opt.draw.vsync);
1405        assert_eq!(opt.pixel.color_format, PixelColorFormat::Rgba8888);
1406        assert!((opt.pixel.gamma - 2.2).abs() < f32::EPSILON);
1407        println!("✅ Builder basic working");
1408    }
1409
1410    #[test]
1411    fn test_builder_from_config() {
1412        let config = make_config();
1413        let opt = PosixOptBuilder::from_config(&config)
1414            .toolkit(ToolkitTheme::Gtk(GtkThemeOpt::default()))
1415            .target_fps(60)
1416            .build_unchecked();
1417
1418        assert_eq!(opt.draw.target_fps, Some(60));
1419        assert!(matches!(opt.draw.toolkit, ToolkitTheme::Gtk(_)));
1420        println!("✅ Builder from_config + override working");
1421    }
1422
1423    #[test]
1424    fn test_xlinx_effective_raster() {
1425        let mut xlinx = XlinxOpt::default();
1426        xlinx.raster_mode = RasterMode::Auto;
1427        xlinx.hw_raster_supported = true;
1428        assert_eq!(xlinx.effective_raster_mode(), RasterMode::Hardware);
1429
1430        xlinx.hw_raster_supported = false;
1431        assert_eq!(xlinx.effective_raster_mode(), RasterMode::Software);
1432        println!("✅ XlinxOpt::effective_raster_mode working");
1433    }
1434
1435    #[test]
1436    fn test_xlinx_uset_permission() {
1437        let mut xlinx = XlinxOpt::default();
1438        xlinx.uset_permission.allow_cache_change = false;
1439
1440        assert!(xlinx.check_uset("cache").is_err());
1441        assert!(xlinx.check_uset("raster").is_ok());
1442        println!("✅ USET permission check working");
1443    }
1444
1445    #[test]
1446    fn test_backend_effective() {
1447        let mut backend = BackendOpt::default();
1448        backend.backend = DisplayBackend::Auto;
1449        backend.wayland_available = true;
1450        assert_eq!(backend.effective_backend(), DisplayBackend::Wayland);
1451
1452        backend.wayland_available = false;
1453        backend.x11_active = true;
1454        assert_eq!(backend.effective_backend(), DisplayBackend::X11);
1455
1456        backend.x11_active = false;
1457        assert_eq!(backend.effective_backend(), DisplayBackend::Headless);
1458        println!("✅ BackendOpt::effective_backend working");
1459    }
1460
1461    #[test]
1462    fn test_xwayland_validation() {
1463        let mut backend = BackendOpt::default();
1464        backend.backend = DisplayBackend::XWayland;
1465        backend.x11_active = false;
1466        assert!(backend.validate_xwayland().is_err());
1467
1468        backend.x11_active = true;
1469        assert!(backend.validate_xwayland().is_ok());
1470        println!("✅ XWayland validation working");
1471    }
1472
1473    #[test]
1474    fn test_x11_settings_active_flag() {
1475        let mut backend = BackendOpt::default();
1476        backend.backend = DisplayBackend::X11;
1477        backend.x11_active = true;
1478        backend.wayland_available = false;
1479        assert!(backend.x11_settings_active());
1480
1481        backend.backend = DisplayBackend::Wayland;
1482        assert!(!backend.x11_settings_active());
1483        println!("✅ X11 settings active flag working");
1484    }
1485
1486    #[test]
1487    fn test_runtime_opt_store() {
1488        let opt = PosixOpt::default();
1489        let store = RuntimeOptStore::new(opt);
1490
1491        store.set_toolkit(ToolkitTheme::Qt(QtThemeOpt::default()));
1492        assert!(matches!(store.read().draw.toolkit, ToolkitTheme::Qt(_)));
1493
1494        store.set_color_format(PixelColorFormat::Rgb565);
1495        assert_eq!(store.read().pixel.color_format, PixelColorFormat::Rgb565);
1496
1497        store.set_cache_mode(XlinxCacheMode::Aggressive).unwrap();
1498        assert_eq!(store.read().xlinx.cache_mode, XlinxCacheMode::Aggressive);
1499
1500        assert!(store.read().source.contains("runtime"));
1501        println!("✅ RuntimeOptStore working");
1502    }
1503
1504    #[test]
1505    fn test_runtime_uset_block() {
1506        let opt = PosixOptBuilder::new()
1507            .xlinx_uset(XlinxUsetPermission {
1508                allow_cache_change: false,
1509                ..XlinxUsetPermission::default()
1510            })
1511            .build_unchecked();
1512
1513        let store = RuntimeOptStore::new(opt);
1514        let result = store.set_cache_mode(XlinxCacheMode::NoCache);
1515        assert!(result.is_err());
1516        println!("✅ USET runtime block working");
1517    }
1518
1519    #[test]
1520    fn test_pixel_color_format_bpp() {
1521        assert_eq!(PixelColorFormat::Rgba8888.bytes_per_pixel(), 4);
1522        assert_eq!(PixelColorFormat::Rgb565.bytes_per_pixel(), 2);
1523        assert_eq!(PixelColorFormat::Rgba16Float.bytes_per_pixel(), 8);
1524        assert_eq!(PixelColorFormat::Luma8.bytes_per_pixel(), 1);
1525        println!("✅ PixelColorFormat bytes_per_pixel working");
1526    }
1527
1528    #[test]
1529    fn test_validation_failures() {
1530        let result = PosixOptBuilder::new()
1531            .min_size(1000, 1000) // min > default (800x600)
1532            .gamma(0.0) // gamma out of range
1533            .build();
1534
1535        assert!(result.is_err());
1536        let errors = result.unwrap_err();
1537        assert!(errors.len() >= 2);
1538        println!("✅ Validation correctly caught {} errors", errors.len());
1539    }
1540
1541    #[test]
1542    fn test_xlinx_arch_auto_detect() {
1543        let arch = XlinxArch::auto_detect();
1544        println!("✅ XlinxArch auto-detected: {}", arch.name());
1545        // Should never panic
1546        assert!(arch.name().len() > 0);
1547    }
1548
1549    #[test]
1550    fn test_grid_options() {
1551        let opt = PosixOptBuilder::new()
1552            .screen_dep_grid(true, true)
1553            .screen_indep_grid(true, 32.0)
1554            .build_unchecked();
1555
1556        assert!(opt.pixel.grid_screen_dep.visible);
1557        assert!(opt.pixel.grid_screen_dep.snap_to_grid);
1558        assert!(opt.pixel.grid_screen_indep.visible);
1559        assert!((opt.pixel.grid_screen_indep.cell_size_logical - 32.0).abs() < f32::EPSILON);
1560        println!("✅ Grid options working");
1561    }
1562
1563    #[test]
1564    fn test_print_summary() {
1565        let opt = PosixOpt::default();
1566        opt.print_summary();
1567        println!("✅ PosixOpt::print_summary working");
1568    }
1569}