1use std::collections::HashMap;
12use std::sync::{Arc, Mutex, RwLock};
13use std::time::SystemTime;
14
15use crate::parser::WasmaConfig;
16use crate::uclient::SectionMemory;
17use crate::wasma_client_unix_posix_raw_app::UClientEngine;
18use crate::wasma_client_unix_posix_raw_window::RawWindowClient;
19use crate::wasma_protocol_unix_posix_opt::{
20 DisplayBackend, PosixOpt, RuntimeOptStore, ToolkitTheme,
21};
22use crate::window_client::WindowClient;
23use crate::window_handling::{WindowGeometry, WindowState, WindowType};
24
25#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum WindowMode {
32 Singularity,
35 Multitary,
38}
39
40impl WindowMode {
41 pub fn name(&self) -> &'static str {
42 match self {
43 Self::Singularity => "singularity",
44 Self::Multitary => "multitary",
45 }
46 }
47
48 pub fn from_config(config: &WasmaConfig) -> Self {
49 if config.uri_handling.singularity_instances {
50 Self::Singularity
51 } else {
52 Self::Multitary
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
63pub enum SpecSource {
64 Manifest(String),
66 Runtime,
68 Config,
70}
71
72impl SpecSource {
73 pub fn label(&self) -> String {
74 match self {
75 Self::Manifest(path) => format!("manifest:{}", path),
76 Self::Runtime => "runtime".to_string(),
77 Self::Config => "config".to_string(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq)]
84pub enum DecorationStyle {
85 ServerSide,
87 ClientSide,
89 None,
91 Auto,
93}
94
95#[derive(Debug, Clone)]
97pub struct WindowSpec {
98 pub app_id: String,
100 pub title: String,
102 pub window_type: WindowType,
104 pub geometry: WindowGeometry,
106 pub decoration: DecorationStyle,
108 pub initial_state: WindowState,
110 pub start_visible: bool,
112 pub start_focused: bool,
114 pub parent_id: Option<u64>,
116 pub properties: HashMap<String, String>,
118 pub source: SpecSource,
120 pub created_at: SystemTime,
122}
123
124impl WindowSpec {
125 pub fn new(app_id: impl Into<String>, title: impl Into<String>) -> Self {
127 Self {
128 app_id: app_id.into(),
129 title: title.into(),
130 window_type: WindowType::Normal,
131 geometry: WindowGeometry {
132 x: 0,
133 y: 0,
134 width: 800,
135 height: 600,
136 },
137 decoration: DecorationStyle::Auto,
138 initial_state: WindowState::Normal,
139 start_visible: true,
140 start_focused: true,
141 parent_id: None,
142 properties: HashMap::new(),
143 source: SpecSource::Runtime,
144 created_at: SystemTime::now(),
145 }
146 }
147
148 pub fn from_config(config: &WasmaConfig) -> Self {
149 let mut spec = Self::new(
150 config.uri_handling.window_app_spec.clone(),
151 "Wasma App".to_string(),
152 );
153 spec.source = SpecSource::Config;
154 spec.geometry = WindowGeometry {
155 x: 0,
156 y: 0,
157 width: config.resource_limits.scope_level.max(1) as u32 * 100 + 800,
158 height: 600,
159 };
160 spec
161 }
162
163 pub fn from_manifest(content: &str, path: impl Into<String>) -> Self {
165 let path = path.into();
166 let mut spec = Self::new("unknown.app", "Untitled");
167 spec.source = SpecSource::Manifest(path);
168
169 for line in content.lines() {
170 let line = line.trim();
171 if line.starts_with('#') || line.is_empty() {
172 continue;
173 }
174 if let Some((key, val)) = line.split_once('=') {
175 let key = key.trim();
176 let val = val.trim();
177 match key {
178 "app_id" => spec.app_id = val.to_string(),
179 "title" => spec.title = val.to_string(),
180 "width" => spec.geometry.width = val.parse().unwrap_or(800),
181 "height" => spec.geometry.height = val.parse().unwrap_or(600),
182 "x" => spec.geometry.x = val.parse().unwrap_or(0),
183 "y" => spec.geometry.y = val.parse().unwrap_or(0),
184 "visible" => spec.start_visible = val == "true",
185 "focused" => spec.start_focused = val == "true",
186 "decoration" => {
187 spec.decoration = match val {
188 "server" => DecorationStyle::ServerSide,
189 "client" => DecorationStyle::ClientSide,
190 "none" => DecorationStyle::None,
191 _ => DecorationStyle::Auto,
192 }
193 }
194 "type" => {
195 spec.window_type = match val {
196 "dialog" => WindowType::Dialog,
197 "utility" => WindowType::Utility,
198 _ => WindowType::Normal,
199 }
200 }
201 "state" => {
202 spec.initial_state = match val {
203 "minimized" => WindowState::Minimized,
204 "maximized" => WindowState::Maximized,
205 "fullscreen" => WindowState::Fullscreen,
206 "hidden" => WindowState::Hidden,
207 _ => WindowState::Normal,
208 }
209 }
210 _ => {
211 spec.properties.insert(key.to_string(), val.to_string());
212 }
213 }
214 }
215 }
216 spec
217 }
218
219 pub fn validate(&self) -> Result<(), Vec<String>> {
221 let mut errors = Vec::new();
222 if self.app_id.is_empty() {
223 errors.push("app_id cannot be empty".to_string());
224 }
225 if self.title.is_empty() {
226 errors.push("title cannot be empty".to_string());
227 }
228 if self.geometry.width == 0 || self.geometry.height == 0 {
229 errors.push(format!(
230 "Invalid geometry: {}x{} (must be > 0)",
231 self.geometry.width, self.geometry.height
232 ));
233 }
234 if errors.is_empty() {
235 Ok(())
236 } else {
237 Err(errors)
238 }
239 }
240}
241
242pub struct WindowSpecBuilder {
244 spec: WindowSpec,
245}
246
247impl WindowSpecBuilder {
248 pub fn new(app_id: impl Into<String>, title: impl Into<String>) -> Self {
249 Self {
250 spec: WindowSpec::new(app_id, title),
251 }
252 }
253
254 pub fn geometry(mut self, x: i32, y: i32, w: u32, h: u32) -> Self {
255 self.spec.geometry = WindowGeometry {
256 x,
257 y,
258 width: w,
259 height: h,
260 };
261 self
262 }
263
264 pub fn window_type(mut self, t: WindowType) -> Self {
265 self.spec.window_type = t;
266 self
267 }
268
269 pub fn decoration(mut self, d: DecorationStyle) -> Self {
270 self.spec.decoration = d;
271 self
272 }
273
274 pub fn initial_state(mut self, s: WindowState) -> Self {
275 self.spec.initial_state = s;
276 self
277 }
278
279 pub fn start_visible(mut self, v: bool) -> Self {
280 self.spec.start_visible = v;
281 self
282 }
283
284 pub fn start_focused(mut self, f: bool) -> Self {
285 self.spec.start_focused = f;
286 self
287 }
288
289 pub fn parent(mut self, id: u64) -> Self {
290 self.spec.parent_id = Some(id);
291 self
292 }
293
294 pub fn property(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
295 self.spec.properties.insert(key.into(), val.into());
296 self
297 }
298
299 pub fn source(mut self, s: SpecSource) -> Self {
300 self.spec.source = s;
301 self
302 }
303
304 pub fn build(self) -> Result<WindowSpec, Vec<String>> {
305 self.spec.validate()?;
306 Ok(self.spec)
307 }
308
309 pub fn build_unchecked(self) -> WindowSpec {
310 self.spec
311 }
312}
313
314#[derive(Debug, Clone)]
320pub struct PosixWindow {
321 pub id: u64,
323 pub spec: WindowSpec,
325 pub geometry: WindowGeometry,
327 pub state: WindowState,
329 pub visible: bool,
331 pub focused: bool,
333 pub applied_opt_source: String,
335 pub launched_at: SystemTime,
337 pub backend: DisplayBackend,
339}
340
341impl PosixWindow {
342 fn from_spec(id: u64, spec: WindowSpec, opt: &PosixOpt) -> Self {
343 let backend = opt.backend.effective_backend();
344 let geometry = WindowGeometry {
345 x: spec.geometry.x,
346 y: spec.geometry.y,
347 width: spec.geometry.width.max(opt.draw.size.min_width),
349 height: spec.geometry.height.max(opt.draw.size.min_height),
350 };
351 let state = spec.initial_state.clone();
352 let visible = spec.start_visible;
353 let focused = spec.start_focused;
354
355 Self {
356 id,
357 geometry,
358 state,
359 visible,
360 focused,
361 applied_opt_source: opt.source.clone(),
362 launched_at: SystemTime::now(),
363 backend,
364 spec,
365 }
366 }
367
368 pub fn is_alive(&self) -> bool {
369 self.visible || matches!(self.state, WindowState::Minimized)
370 }
371}
372
373pub struct PosixWindowLauncher {
389 config: Arc<WasmaConfig>,
390
391 opt_store: Arc<RuntimeOptStore>,
393
394 mode: WindowMode,
396
397 windows: Arc<RwLock<HashMap<u64, PosixWindow>>>,
399
400 next_id: Arc<Mutex<u64>>,
402
403 window_client: Option<Arc<Mutex<WindowClient>>>,
405
406 raw_client: Option<Arc<Mutex<RawWindowClient>>>,
408
409 active: bool,
411
412 memory: SectionMemory,
414}
415
416impl PosixWindowLauncher {
417 pub fn new(config: WasmaConfig) -> Self {
418 let mode = WindowMode::from_config(&config);
419 let opt = PosixOpt::from_config(&config);
420 let level = config.resource_limits.scope_level;
421
422 Self {
423 mode,
424 opt_store: Arc::new(RuntimeOptStore::new(opt)),
425 windows: Arc::new(RwLock::new(HashMap::new())),
426 next_id: Arc::new(Mutex::new(1)),
427 window_client: None,
428 raw_client: None,
429 active: false,
430 memory: SectionMemory::new(level),
431 config: Arc::new(config),
432 }
433 }
434
435 pub fn from_config(config: Arc<WasmaConfig>) -> Self {
436 let mode = WindowMode::from_config(&config);
437 let opt = PosixOpt::from_config(&config);
438 let level = config.resource_limits.scope_level;
439
440 Self {
441 mode,
442 opt_store: Arc::new(RuntimeOptStore::new(opt)),
443 windows: Arc::new(RwLock::new(HashMap::new())),
444 next_id: Arc::new(Mutex::new(1)),
445 window_client: None,
446 raw_client: None,
447 active: false,
448 memory: SectionMemory::new(level),
449 config,
450 }
451 }
452
453 pub fn attach_window_client(&mut self, wc: Arc<Mutex<WindowClient>>) {
459 self.window_client = Some(wc);
460 println!("🔗 PosixWindowLauncher: WindowClient attached (rendering delegate)");
461 }
462
463 pub fn attach_raw_client(&mut self, rc: Arc<Mutex<RawWindowClient>>) {
465 self.raw_client = Some(rc);
466 println!("🔗 PosixWindowLauncher: RawWindowClient attached (low-level delegate)");
467 }
468
469 pub fn detach_window_client(&mut self) {
471 self.window_client = None;
472 println!("🔌 PosixWindowLauncher: WindowClient detached");
473 }
474
475 pub fn detach_raw_client(&mut self) {
477 self.raw_client = None;
478 println!("🔌 PosixWindowLauncher: RawWindowClient detached");
479 }
480
481 pub fn set_mode(&mut self, mode: WindowMode) {
487 println!(
488 "🔄 PosixWindowLauncher: Mode {} → {}",
489 self.mode.name(),
490 mode.name()
491 );
492 self.mode = mode;
493 }
494
495 pub fn mode(&self) -> WindowMode {
496 self.mode
497 }
498
499 pub fn override_opt<F>(&self, f: F)
505 where
506 F: FnOnce(&mut PosixOpt),
507 {
508 self.opt_store.override_with(f);
509 }
510
511 pub fn opt_store(&self) -> Arc<RuntimeOptStore> {
512 self.opt_store.clone()
513 }
514
515 fn alloc_id(&self) -> u64 {
520 let mut n = self.next_id.lock().unwrap();
521 let id = *n;
522 *n += 1;
523 id
524 }
525
526 pub fn launch(&self, spec: WindowSpec) -> Result<u64, String> {
534 spec.validate().map_err(|e| e.join(", "))?;
536
537 if self.mode == WindowMode::Singularity {
539 let ids: Vec<u64> = { self.windows.read().unwrap().keys().cloned().collect() };
540 for id in ids {
541 self.close_window(id).ok();
542 }
543 }
544
545 let id = self.alloc_id();
546 let opt = self.opt_store.read();
547
548 let window = PosixWindow::from_spec(id, spec.clone(), &opt);
550
551 println!(
552 "🪟 PosixWindowLauncher [{}]: Launching window {} — '{}' [{}x{}] via {} (source: {})",
553 self.mode.name(),
554 id,
555 spec.title,
556 window.geometry.width,
557 window.geometry.height,
558 window.backend.name(),
559 spec.source.label(),
560 );
561
562 if let Some(ref rc) = self.raw_client {
564 let raw = rc.lock().unwrap();
565 match raw.create_window(
566 &spec.title,
567 &spec.app_id,
568 window.geometry,
569 spec.window_type.clone(),
570 ) {
571 Ok(raw_id) => println!(" ↳ RawWindowClient delegate: raw window id={}", raw_id),
572 Err(e) => println!(" ⚠️ RawWindowClient delegate failed (non-fatal): {}", e),
573 }
574 }
575
576 if let Some(ref wc) = self.window_client {
578 let mut client = wc.lock().unwrap();
579 client.resize(window.geometry.width, window.geometry.height);
580 println!(
581 " ↳ WindowClient rendering configured: {}x{}",
582 window.geometry.width, window.geometry.height
583 );
584 }
585
586 self.apply_toolkit_theme(&opt, id);
588
589 self.windows.write().unwrap().insert(id, window);
591 Ok(id)
592 }
593
594 pub fn launch_from_config(&self) -> Result<u64, String> {
596 let spec = WindowSpec::from_config(&self.config);
597 println!("📋 PosixWindowLauncher: Launching from config");
598 self.launch(spec)
599 }
600
601 pub fn launch_from_manifest(
603 &self,
604 content: &str,
605 path: impl Into<String>,
606 ) -> Result<u64, String> {
607 let path = path.into();
608 let spec = WindowSpec::from_manifest(content, &path);
609 println!("📄 PosixWindowLauncher: Launching from manifest: {}", path);
610 self.launch(spec)
611 }
612
613 fn apply_toolkit_theme(&self, opt: &PosixOpt, window_id: u64) {
615 match &opt.draw.toolkit {
616 ToolkitTheme::Gtk(gtk) => {
617 println!(
618 " 🎨 Toolkit: GTK{} theme='{}' dark={}",
619 gtk.version, gtk.theme_name, gtk.dark_mode
620 );
621 }
622 ToolkitTheme::Iced(iced) => {
623 println!(" 🎨 Toolkit: Iced theme={:?}", iced.variant);
624 }
625 ToolkitTheme::Qt(qt) => {
626 println!(
627 " 🎨 Toolkit: Qt{} style='{}' dark={}",
628 qt.version, qt.style_name, qt.dark_mode
629 );
630 }
631 ToolkitTheme::None => {
632 println!(" 🎨 Toolkit: None (raw drawing) for window {}", window_id);
633 }
634 }
635 }
636
637 pub fn close_window(&self, id: u64) -> Result<(), String> {
643 if let Some(ref rc) = self.raw_client {
645 let raw = rc.lock().unwrap();
646 let _ = raw.destroy_window(id);
647 }
648
649 let mut windows = self.windows.write().unwrap();
650 if windows.remove(&id).is_some() {
651 println!("🗑️ PosixWindowLauncher: Window {} closed", id);
652 Ok(())
653 } else {
654 Err(format!("Window {} not found", id))
655 }
656 }
657
658 pub fn close_all(&self) {
660 let ids: Vec<u64> = self.windows.read().unwrap().keys().cloned().collect();
661 for id in ids {
662 self.close_window(id).ok();
663 }
664 println!("🗑️ PosixWindowLauncher: All windows closed");
665 }
666
667 pub fn set_geometry(&self, id: u64, geo: WindowGeometry) -> Result<(), String> {
669 if let Some(ref rc) = self.raw_client {
671 let raw = rc.lock().unwrap();
672 let _ = raw.set_geometry(id, geo);
673 }
674
675 let mut windows = self.windows.write().unwrap();
676 match windows.get_mut(&id) {
677 Some(w) => {
678 w.geometry = geo;
679 if let Some(ref wc) = self.window_client {
681 let mut client = wc.lock().unwrap();
682 client.resize(geo.width, geo.height);
683 }
684 Ok(())
685 }
686 None => Err(format!("Window {} not found", id)),
687 }
688 }
689
690 pub fn set_state(&self, id: u64, state: WindowState) -> Result<(), String> {
692 if let Some(ref rc) = self.raw_client {
693 let raw = rc.lock().unwrap();
694 let _ = raw.set_state(id, state.clone());
695 }
696 let mut windows = self.windows.write().unwrap();
697 match windows.get_mut(&id) {
698 Some(w) => {
699 w.state = state;
700 Ok(())
701 }
702 None => Err(format!("Window {} not found", id)),
703 }
704 }
705
706 pub fn set_focus(&self, id: u64) -> Result<(), String> {
708 {
710 let mut windows = self.windows.write().unwrap();
711 for w in windows.values_mut() {
712 w.focused = false;
713 }
714 }
715 if let Some(ref rc) = self.raw_client {
716 let raw = rc.lock().unwrap();
717 let _ = raw.set_focus(id);
718 }
719 let mut windows = self.windows.write().unwrap();
720 match windows.get_mut(&id) {
721 Some(w) => {
722 w.focused = true;
723 Ok(())
724 }
725 None => Err(format!("Window {} not found", id)),
726 }
727 }
728
729 pub fn set_visible(&self, id: u64, visible: bool) -> Result<(), String> {
731 if let Some(ref rc) = self.raw_client {
732 let raw = rc.lock().unwrap();
733 let _ = raw.set_visible(id, visible);
734 }
735 let mut windows = self.windows.write().unwrap();
736 match windows.get_mut(&id) {
737 Some(w) => {
738 w.visible = visible;
739 Ok(())
740 }
741 None => Err(format!("Window {} not found", id)),
742 }
743 }
744
745 pub fn set_title(&self, id: u64, title: impl Into<String>) -> Result<(), String> {
747 let title = title.into();
748 if let Some(ref rc) = self.raw_client {
749 let raw = rc.lock().unwrap();
750 let _ = raw.set_title(id, &title);
751 }
752 let mut windows = self.windows.write().unwrap();
753 match windows.get_mut(&id) {
754 Some(w) => {
755 w.spec.title = title;
756 Ok(())
757 }
758 None => Err(format!("Window {} not found", id)),
759 }
760 }
761
762 pub fn render_frame(&self, id: u64, data: &[u8]) -> Result<(), String> {
765 let visible = {
766 let windows = self.windows.read().unwrap();
767 match windows.get(&id) {
768 Some(w) => w.visible && !matches!(w.state, WindowState::Hidden),
769 None => return Err(format!("Window {} not found", id)),
770 }
771 };
772 if !visible {
773 return Ok(());
774 }
775
776 self.dispatch_data(data);
778 Ok(())
779}
780 pub fn get_window(&self, id: u64) -> Option<PosixWindow> {
785 self.windows.read().unwrap().get(&id).cloned()
786 }
787
788 pub fn list_windows(&self) -> Vec<PosixWindow> {
789 let windows = self.windows.read().unwrap();
790 let mut v: Vec<_> = windows.values().cloned().collect();
791 v.sort_by_key(|w| w.id);
792 v
793 }
794
795 pub fn window_count(&self) -> usize {
796 self.windows.read().unwrap().len()
797 }
798
799 pub fn focused_window(&self) -> Option<PosixWindow> {
800 let windows = self.windows.read().unwrap();
801 windows.values().find(|w| w.focused).cloned()
802 }
803
804 pub fn has_window_client(&self) -> bool {
805 self.window_client.is_some()
806 }
807 pub fn has_raw_client(&self) -> bool {
808 self.raw_client.is_some()
809 }
810}
811
812impl UClientEngine for PosixWindowLauncher {
817 fn start_engine(&mut self) -> Result<(), Box<dyn std::error::Error>> {
818 self.active = true;
819
820 let opt = self.opt_store.read();
821 println!("🟢 WASMA PosixWindowLauncher: Engine Started");
822 println!(" Mode: {}", self.mode.name());
823 println!(" Backend: {}", opt.backend.effective_backend().name());
824 println!(" Toolkit: {}", opt.draw.toolkit.name());
825 println!(
826 " Xlinx: {} | cache={}",
827 opt.xlinx.arch.name(),
828 opt.xlinx.cache_mode.name()
829 );
830 println!(
831 " Delegates: WindowClient={} RawClient={}",
832 self.window_client.is_some(),
833 self.raw_client.is_some()
834 );
835 drop(opt);
836
837 if self.windows.read().unwrap().is_empty() {
839 println!(" Auto-launching from config...");
840 match self.launch_from_config() {
841 Ok(id) => println!(" Auto-launch OK: window id={}", id),
842 Err(e) => eprintln!(" ⚠️ Auto-launch failed: {}", e),
843 }
844 }
845
846 loop {
848 if !self.active {
849 break;
850 }
851 std::thread::sleep(std::time::Duration::from_millis(16)); }
853
854 Ok(())
855 }
856
857 fn dispatch_data(&self, data: &[u8]) {
858 if let Some(w) = self.focused_window() {
860 let _ = self.render_frame(w.id, data);
861 }
862 }
863
864 fn memory_usage(&self) -> (usize, usize, usize) {
865 (
866 self.memory.raw_storage.len(),
867 self.memory.cell_count,
868 self.memory.cell_size,
869 )
870 }
871
872 fn get_config(&self) -> &WasmaConfig {
873 &self.config
874 }
875
876 fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error>> {
877 self.active = false;
878 self.close_all();
879 println!("🛑 PosixWindowLauncher: Shutdown complete");
880 Ok(())
881 }
882
883 fn is_active(&self) -> bool {
884 self.active
885 }
886}
887
888pub struct PosixWindowLauncherBuilder {
893 config: Option<WasmaConfig>,
894 mode: Option<WindowMode>,
895 opt_overrides: Vec<Box<dyn FnOnce(&mut PosixOpt)>>,
896 window_client: Option<Arc<Mutex<WindowClient>>>,
897 raw_client: Option<Arc<Mutex<RawWindowClient>>>,
898}
899
900impl PosixWindowLauncherBuilder {
901 pub fn new() -> Self {
902 Self {
903 config: None,
904 mode: None,
905 opt_overrides: Vec::new(),
906 window_client: None,
907 raw_client: None,
908 }
909 }
910
911 pub fn with_config(mut self, config: WasmaConfig) -> Self {
912 self.config = Some(config);
913 self
914 }
915
916 pub fn with_mode(mut self, mode: WindowMode) -> Self {
917 self.mode = Some(mode);
918 self
919 }
920
921 pub fn with_window_client(mut self, wc: Arc<Mutex<WindowClient>>) -> Self {
922 self.window_client = Some(wc);
923 self
924 }
925
926 pub fn with_raw_client(mut self, rc: Arc<Mutex<RawWindowClient>>) -> Self {
927 self.raw_client = Some(rc);
928 self
929 }
930
931 pub fn opt_override<F: FnOnce(&mut PosixOpt) + 'static>(mut self, f: F) -> Self {
932 self.opt_overrides.push(Box::new(f));
933 self
934 }
935
936 pub fn build(self) -> Result<PosixWindowLauncher, String> {
937 let config = self.config.ok_or("WasmaConfig required")?;
938 let mut launcher = PosixWindowLauncher::new(config);
939
940 if let Some(mode) = self.mode {
941 launcher.mode = mode;
942 }
943
944 for f in self.opt_overrides {
945 launcher.opt_store.override_with(f);
946 }
947
948 if let Some(wc) = self.window_client {
949 launcher.attach_window_client(wc);
950 }
951 if let Some(rc) = self.raw_client {
952 launcher.attach_raw_client(rc);
953 }
954
955 Ok(launcher)
956 }
957}
958
959impl Default for PosixWindowLauncherBuilder {
960 fn default() -> Self {
961 Self::new()
962 }
963}
964
965#[cfg(test)]
970mod tests {
971 use super::*;
972 use crate::parser::ConfigParser;
973 use crate::wasma_protocol_unix_posix_opt::XlinxCacheMode;
974
975 fn make_config() -> WasmaConfig {
976 let parser = ConfigParser::new(None);
977 let content = parser.generate_default_config();
978 parser.parse(&content).unwrap()
979 }
980
981 fn make_launcher() -> PosixWindowLauncher {
982 PosixWindowLauncher::new(make_config())
983 }
984
985 #[test]
986 fn test_launcher_creation() {
987 let launcher = make_launcher();
988 assert!(!launcher.is_active());
989 assert_eq!(launcher.window_count(), 0);
990 assert!(!launcher.has_window_client());
991 assert!(!launcher.has_raw_client());
992 println!(
993 "✅ PosixWindowLauncher creation working (mode: {})",
994 launcher.mode().name()
995 );
996 }
997
998 #[test]
999 fn test_launch_runtime_spec() {
1000 let launcher = make_launcher();
1001 let spec = WindowSpecBuilder::new("test.app", "Test Window")
1002 .geometry(100, 100, 1280, 720)
1003 .window_type(WindowType::Normal)
1004 .decoration(DecorationStyle::ServerSide)
1005 .build()
1006 .unwrap();
1007
1008 let id = launcher.launch(spec).unwrap();
1009 assert_eq!(launcher.window_count(), 1);
1010
1011 let window = launcher.get_window(id).unwrap();
1012 assert_eq!(window.spec.app_id, "test.app");
1013 assert_eq!(window.geometry.width, 1280);
1014 assert_eq!(window.geometry.height, 720);
1015 println!("✅ Runtime spec launch working: id={}", id);
1016 }
1017
1018 #[test]
1019 fn test_launch_from_config() {
1020 let launcher = make_launcher();
1021 let id = launcher.launch_from_config().unwrap();
1022 assert_eq!(launcher.window_count(), 1);
1023 assert!(launcher.get_window(id).is_some());
1024 println!("✅ Config-based launch working: id={}", id);
1025 }
1026
1027 #[test]
1028 fn test_launch_from_manifest() {
1029 let launcher = make_launcher();
1030 let manifest = "
1031# Test manifest
1032app_id=manifest.app
1033title=Manifest Window
1034width=1024
1035height=768
1036decoration=client
1037visible=true
1038focused=false
1039custom_key=custom_value
1040";
1041 let id = launcher
1042 .launch_from_manifest(manifest, "/etc/wasma/test.spec")
1043 .unwrap();
1044 let window = launcher.get_window(id).unwrap();
1045 assert_eq!(window.spec.app_id, "manifest.app");
1046 assert_eq!(window.spec.title, "Manifest Window");
1047 assert_eq!(window.geometry.width, 1024);
1048 assert_eq!(window.geometry.height, 768);
1049 assert!(!window.focused);
1050 assert_eq!(
1051 window.spec.properties.get("custom_key").map(|s| s.as_str()),
1052 Some("custom_value")
1053 );
1054 println!("✅ Manifest launch working: id={}", id);
1055 }
1056
1057 #[test]
1058 fn test_singularity_mode_replaces() {
1059 let mut launcher = make_launcher();
1060 launcher.set_mode(WindowMode::Singularity);
1061
1062 let spec1 = WindowSpecBuilder::new("app1", "Win1").build_unchecked();
1063 let spec2 = WindowSpecBuilder::new("app2", "Win2").build_unchecked();
1064 let spec3 = WindowSpecBuilder::new("app3", "Win3").build_unchecked();
1065
1066 launcher.launch(spec1).unwrap();
1067 assert_eq!(launcher.window_count(), 1);
1068 launcher.launch(spec2).unwrap();
1069 assert_eq!(launcher.window_count(), 1); launcher.launch(spec3).unwrap();
1071 assert_eq!(launcher.window_count(), 1); let w = launcher.list_windows();
1074 assert_eq!(w[0].spec.app_id, "app3");
1075 println!("✅ Singularity mode replacement working");
1076 }
1077
1078 #[test]
1079 fn test_multitary_mode_accumulates() {
1080 let mut launcher = make_launcher();
1081 launcher.set_mode(WindowMode::Multitary);
1082
1083 for i in 0..4 {
1084 let spec =
1085 WindowSpecBuilder::new(format!("app{}", i), format!("Win{}", i)).build_unchecked();
1086 launcher.launch(spec).unwrap();
1087 }
1088 assert_eq!(launcher.window_count(), 4);
1089 println!(
1090 "✅ Multitary mode accumulation working: {} windows",
1091 launcher.window_count()
1092 );
1093 }
1094
1095 #[test]
1096 fn test_mode_switch_at_runtime() {
1097 let mut launcher = make_launcher();
1098 launcher.set_mode(WindowMode::Multitary);
1099
1100 for i in 0..3 {
1101 let spec =
1102 WindowSpecBuilder::new(format!("a{}", i), format!("W{}", i)).build_unchecked();
1103 launcher.launch(spec).unwrap();
1104 }
1105 assert_eq!(launcher.window_count(), 3);
1106
1107 launcher.set_mode(WindowMode::Singularity);
1109 let spec = WindowSpecBuilder::new("final.app", "Final").build_unchecked();
1110 launcher.launch(spec).unwrap();
1111 assert_eq!(launcher.window_count(), 1);
1112 println!("✅ Runtime mode switch working");
1113 }
1114
1115 #[test]
1116 fn test_window_operations() {
1117 let launcher = make_launcher();
1118 let spec = WindowSpecBuilder::new("ops.app", "Ops Window")
1119 .geometry(0, 0, 800, 600)
1120 .build_unchecked();
1121 let id = launcher.launch(spec).unwrap();
1122
1123 let new_geo = WindowGeometry {
1125 x: 50,
1126 y: 50,
1127 width: 1920,
1128 height: 1080,
1129 };
1130 launcher.set_geometry(id, new_geo).unwrap();
1131 assert_eq!(launcher.get_window(id).unwrap().geometry.width, 1920);
1132
1133 launcher.set_state(id, WindowState::Maximized).unwrap();
1135 assert_eq!(
1136 launcher.get_window(id).unwrap().state,
1137 WindowState::Maximized
1138 );
1139
1140 launcher.set_visible(id, false).unwrap();
1142 assert!(!launcher.get_window(id).unwrap().visible);
1143
1144 launcher.set_title(id, "Updated Title").unwrap();
1146 assert_eq!(launcher.get_window(id).unwrap().spec.title, "Updated Title");
1147
1148 println!("✅ Window operations working");
1149 }
1150
1151 #[test]
1152 fn test_focus_management() {
1153 let mut launcher = make_launcher();
1154 launcher.set_mode(WindowMode::Multitary);
1155
1156 let id1 = launcher
1157 .launch(WindowSpecBuilder::new("a1", "W1").build_unchecked())
1158 .unwrap();
1159 let id2 = launcher
1160 .launch(WindowSpecBuilder::new("a2", "W2").build_unchecked())
1161 .unwrap();
1162
1163 launcher.set_focus(id1).unwrap();
1164 assert!(launcher.get_window(id1).unwrap().focused);
1165 assert!(!launcher.get_window(id2).unwrap().focused);
1166 assert_eq!(launcher.focused_window().unwrap().id, id1);
1167
1168 launcher.set_focus(id2).unwrap();
1169 assert!(!launcher.get_window(id1).unwrap().focused);
1170 assert_eq!(launcher.focused_window().unwrap().id, id2);
1171
1172 println!("✅ Focus management working");
1173 }
1174
1175 #[test]
1176 fn test_close_window() {
1177 let launcher = make_launcher();
1178 let id = launcher.launch_from_config().unwrap();
1179 assert_eq!(launcher.window_count(), 1);
1180 launcher.close_window(id).unwrap();
1181 assert_eq!(launcher.window_count(), 0);
1182 assert!(launcher.get_window(id).is_none());
1183 println!("✅ Window close working");
1184 }
1185
1186 #[test]
1187 fn test_close_all() {
1188 let mut launcher = make_launcher();
1189 launcher.set_mode(WindowMode::Multitary);
1190 for i in 0..5 {
1191 launcher
1192 .launch(
1193 WindowSpecBuilder::new(format!("a{}", i), format!("W{}", i)).build_unchecked(),
1194 )
1195 .unwrap();
1196 }
1197 assert_eq!(launcher.window_count(), 5);
1198 launcher.close_all();
1199 assert_eq!(launcher.window_count(), 0);
1200 println!("✅ close_all working");
1201 }
1202
1203 #[test]
1204 fn test_opt_override_applied() {
1205 let launcher = make_launcher();
1206 launcher.override_opt(|opt| {
1207 opt.draw.vsync = false;
1208 opt.draw.target_fps = Some(144);
1209 opt.xlinx.cache_mode = XlinxCacheMode::Aggressive;
1210 });
1211
1212 let opt = launcher.opt_store.read();
1213 assert!(!opt.draw.vsync);
1214 assert_eq!(opt.draw.target_fps, Some(144));
1215 assert_eq!(opt.xlinx.cache_mode, XlinxCacheMode::Aggressive);
1216 println!("✅ Opt override applied correctly");
1217 }
1218
1219 #[test]
1220 fn test_posix_opt_min_size_constraint() {
1221 let config = make_config();
1222 let mut launcher = PosixWindowLauncher::new(config);
1223 launcher.override_opt(|opt| {
1225 opt.draw.size.min_width = 500;
1226 opt.draw.size.min_height = 400;
1227 });
1228
1229 let spec = WindowSpecBuilder::new("small.app", "Small")
1231 .geometry(0, 0, 100, 100) .build_unchecked();
1233 let id = launcher.launch(spec).unwrap();
1234 let w = launcher.get_window(id).unwrap();
1235 assert!(w.geometry.width >= 500);
1237 assert!(w.geometry.height >= 400);
1238 println!(
1239 "✅ PosixOpt min size constraint applied: {}x{}",
1240 w.geometry.width, w.geometry.height
1241 );
1242 }
1243
1244 #[test]
1245 fn test_spec_from_manifest_invalid_values() {
1246 let launcher = make_launcher();
1247 let manifest = "
1248app_id=broken.app
1249title=Broken
1250width=notanumber
1251height=600
1252";
1253 let id = launcher
1254 .launch_from_manifest(manifest, "/tmp/broken.spec")
1255 .unwrap();
1256 let w = launcher.get_window(id).unwrap();
1257 assert_eq!(w.spec.geometry.width, 800);
1259 println!("✅ Manifest invalid value fallback working");
1260 }
1261
1262 #[test]
1263 fn test_uclient_engine_trait_object() {
1264 let launcher: Box<dyn UClientEngine> = Box::new(make_launcher());
1265 assert!(!launcher.is_active());
1266 let (total, cells, _) = launcher.memory_usage();
1267 assert!(total > 0 && cells > 0);
1268 println!("✅ UClientEngine trait object (PosixWindowLauncher) working");
1269 }
1270
1271 #[test]
1272 fn test_builder() {
1273 let config = make_config();
1274 let launcher = PosixWindowLauncherBuilder::new()
1275 .with_config(config)
1276 .with_mode(WindowMode::Multitary)
1277 .opt_override(|opt| opt.draw.antialiasing = false)
1278 .build()
1279 .unwrap();
1280
1281 assert_eq!(launcher.mode(), WindowMode::Multitary);
1282 assert!(!launcher.opt_store.read().draw.antialiasing);
1283 println!("✅ PosixWindowLauncherBuilder working");
1284 }
1285
1286 #[test]
1287 fn test_list_windows_sorted() {
1288 let mut launcher = make_launcher();
1289 launcher.set_mode(WindowMode::Multitary);
1290 for i in 0..4 {
1291 launcher
1292 .launch(
1293 WindowSpecBuilder::new(format!("a{}", i), format!("W{}", i)).build_unchecked(),
1294 )
1295 .unwrap();
1296 }
1297 let windows = launcher.list_windows();
1298 assert_eq!(windows.len(), 4);
1299 for pair in windows.windows(2) {
1300 assert!(pair[0].id < pair[1].id);
1301 }
1302 println!("✅ list_windows sorted correctly");
1303 }
1304}