1use crate::parser::WasmaConfig;
11use crate::wasma_protocol_universal_client_unix_posix_window::{ArchKind, CURRENT_ARCH};
12use std::sync::{Arc, RwLock};
13
14#[derive(Debug, Clone, PartialEq)]
20pub enum ToolkitTheme {
21 Gtk(GtkThemeOpt),
23 Iced(IcedThemeOpt),
25 Qt(QtThemeOpt),
27 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#[derive(Debug, Clone, PartialEq)]
44pub struct GtkThemeOpt {
45 pub theme_name: String,
47 pub version: u8,
49 pub dark_mode: bool,
51 pub font_scale: f32,
53 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#[derive(Debug, Clone, PartialEq)]
71pub struct IcedThemeOpt {
72 pub variant: IcedThemeVariant,
74 pub accent_color: Option<[u8; 4]>,
76 pub font_family: Option<String>,
78 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#[derive(Debug, Clone, PartialEq)]
110pub struct QtThemeOpt {
111 pub style_name: String,
113 pub version: u8,
115 pub dark_mode: bool,
117 pub platform_theme: Option<String>,
119 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#[derive(Debug, Clone, PartialEq)]
137pub struct DrawSizeOpt {
138 pub min_width: u32,
140 pub min_height: u32,
142 pub max_width: Option<u32>,
144 pub max_height: Option<u32>,
146 pub default_width: u32,
148 pub default_height: u32,
150 pub aspect_ratio: Option<f32>,
152 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#[derive(Debug, Clone)]
173pub struct DrawOpt {
174 pub size: DrawSizeOpt,
175 pub toolkit: ToolkitTheme,
176 pub antialiasing: bool,
178 pub subpixel_rendering: bool,
180 pub vsync: bool,
182 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#[derive(Debug, Clone, Copy, PartialEq)]
205pub enum PixelColorFormat {
206 Rgb888,
207 Rgba8888,
208 Bgr888,
209 Bgra8888,
210 Rgb565,
211 Rgb101010,
213 Rgba16Float,
214 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#[derive(Debug, Clone, PartialEq)]
249pub struct PixelGridScreenDep {
250 pub cell_width_px: u32,
252 pub cell_height_px: u32,
254 pub line_color: [u8; 4],
256 pub line_width_phys: f32,
258 pub visible: bool,
260 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#[derive(Debug, Clone, PartialEq)]
280pub struct PixelGridScreenIndep {
281 pub cell_size_logical: f32,
283 pub subdivisions: u8,
285 pub major_every: u32,
287 pub major_color: [u8; 4],
289 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#[derive(Debug, Clone, PartialEq)]
309pub struct PixelSizeOpt {
310 pub logical_scale: f32,
312 pub physical_size_um: f32,
314 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#[derive(Debug, Clone)]
330pub struct PixelOpt {
331 pub size: PixelSizeOpt,
332 pub color_format: PixelColorFormat,
333 pub background_color: [u8; 4],
335 pub clear_color: [u8; 4],
337 pub grid_screen_dep: PixelGridScreenDep,
338 pub grid_screen_indep: PixelGridScreenIndep,
339 pub pixel_perfect: bool,
341 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#[derive(Debug, Clone, Copy, PartialEq)]
366pub enum XlinxCacheMode {
367 Auto,
369 NoCache,
371 Manual,
373 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#[derive(Debug, Clone, Copy, PartialEq)]
399pub enum RasterMode {
400 Debug,
403 Hardware,
405 Software,
407 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#[derive(Debug, Clone, Copy, PartialEq)]
438pub enum XlinxArch {
439 X86_64,
441 Aarch64,
443 RiscV,
445 Generic,
447 SoftwareOnly,
449 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#[derive(Debug, Clone, PartialEq)]
490pub struct XlinxUsetPermission {
491 pub allow_cache_change: bool,
493 pub allow_raster_change: bool,
495 pub allow_arch_change: bool,
497 pub allow_sw_fallback: bool,
499 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, allow_sw_fallback: true,
510 allow_log_config: true,
511 }
512 }
513}
514
515#[derive(Debug, Clone, PartialEq)]
517pub struct XlinxAutoLog {
518 pub enabled: bool,
520 pub level: u8,
522 pub output_path: Option<String>,
524 pub log_frame_timing: bool,
526 pub log_cache_stats: bool,
528 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#[derive(Debug, Clone, PartialEq)]
548pub struct XlinxSoftFallback {
549 pub enabled: bool,
551 pub thread_count: u8,
553 pub tile_width: u32,
555 pub tile_height: u32,
556 pub software_msaa: bool,
558 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#[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 pub hw_raster_supported: bool,
587 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 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 pub fn is_debug_raster(&self) -> bool {
626 cfg!(debug_assertions) && matches!(self.raster_mode, RasterMode::Debug)
627 }
628
629 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#[derive(Debug, Clone, PartialEq)]
658pub enum DisplayBackend {
659 X11,
661 Wayland,
663 XWayland,
666 Auto,
668 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#[derive(Debug, Clone, PartialEq)]
701pub struct X11DebugOpt {
702 pub xcb_debug: bool,
704 pub log_events: bool,
706 pub sync_mode: bool,
708 pub debug_border_width: u8,
710 pub decorations: bool,
712 pub use_composite: bool,
714 pub override_redirect: bool,
716 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#[derive(Debug, Clone, PartialEq)]
745pub struct WaylandSurfaceOpt {
746 pub subsurface_support: bool,
748 pub preferred_format: PixelColorFormat,
750 pub fractional_scale: bool,
752 pub server_side_decorations: bool,
754 pub layer_shell: bool,
756 pub presentation_time: bool,
758 pub explicit_sync: bool,
760 pub dmabuf: bool,
762 pub output_scale: u32,
764 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#[derive(Debug, Clone, PartialEq)]
788pub struct X11ToWaylandOpt {
789 pub auto_switch: bool,
791 pub min_compositor_version: u32,
793 pub fallback_to_x11: bool,
795 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#[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 pub x11_active: bool,
819 pub wayland_available: bool,
821}
822
823impl Default for BackendOpt {
824 fn default() -> Self {
825 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 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 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 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 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#[derive(Debug, Clone)]
897pub struct PosixOpt {
898 pub draw: DrawOpt,
899 pub pixel: PixelOpt,
900 pub xlinx: XlinxOpt,
901 pub backend: BackendOpt,
902 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 pub fn from_config(config: &WasmaConfig) -> Self {
921 let mut opt = Self::default();
922 opt.source = "wasma.in.conf".to_string();
923
924 opt.draw.size.default_width = 800;
926 opt.draw.size.default_height = 600;
927
928 opt.xlinx.arch = XlinxArch::auto_detect();
930
931 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 if config.resource_limits.scope_level == 0 {
940 opt.xlinx.cache_mode = XlinxCacheMode::NoCache;
941 }
942
943 opt.draw.toolkit = ToolkitTheme::Iced(IcedThemeOpt::default());
945
946 if config.uri_handling.singularity_instances {
948 opt.draw.vsync = true;
949 }
950
951 opt
952 }
953
954 pub fn validate(&self) -> Result<(), Vec<String>> {
956 let mut errors = Vec::new();
957
958 if let Err(e) = self.backend.validate_xwayland() {
960 errors.push(e);
961 }
962
963 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 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 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 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 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
1080pub 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 pub fn read(&self) -> std::sync::RwLockReadGuard<'_, PosixOpt> {
1099 self.inner.read().unwrap()
1100 }
1101
1102 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 pub fn set_toolkit(&self, toolkit: ToolkitTheme) {
1114 self.override_with(|o| o.draw.toolkit = toolkit);
1115 }
1116
1117 pub fn set_backend(&self, backend: DisplayBackend) {
1119 self.override_with(|o| o.backend.backend = backend);
1120 }
1121
1122 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 pub fn set_raster_mode(&self, mode: RasterMode) -> Result<(), String> {
1132 #[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 pub fn set_color_format(&self, fmt: PixelColorFormat) {
1144 self.override_with(|o| o.pixel.color_format = fmt);
1145 }
1146
1147 pub fn set_screen_dep_grid(&self, visible: bool) {
1149 self.override_with(|o| o.pixel.grid_screen_dep.visible = visible);
1150 }
1151
1152 pub fn set_screen_indep_grid(&self, visible: bool) {
1154 self.override_with(|o| o.pixel.grid_screen_indep.visible = visible);
1155 }
1156
1157 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
1174pub 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 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 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 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 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 pub fn build(self) -> Result<PosixOpt, Vec<String>> {
1337 self.opt.validate()?;
1338 Ok(self.opt)
1339 }
1340
1341 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#[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) .gamma(0.0) .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 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}