Skip to main content

wasma_sys/
wasma_protocol_unix_posix_window.rs

1// WASMA - Windows Assignment System Monitoring Architecture
2// wasma_protocol_unix_posix_window.rs
3// Normal (non-raw) POSIX Window Launch Protocol
4// Manages window creation, lifecycle, and rendering via direct client specification.
5// Launch triggers: client spec (manifest/config) or runtime API call — both supported.
6// Window mode: Singularity (single) or Multitary (multi), runtime selection.
7// Integrations: PosixOpt (auto), WindowClient (optional), RawWindowClient (optional delegate)
8// Implements UClientEngine.
9// January 2026
10
11use 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// ============================================================================
26// WINDOW MODE — Singularity or Multitary
27// ============================================================================
28
29/// Window mode — controls how many windows can be active simultaneously
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum WindowMode {
32    /// Singularity: only one window active at a time
33    /// New launch replaces the existing window
34    Singularity,
35    /// Multitary: multiple windows can coexist
36    /// Each launch creates a new independent window
37    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// ============================================================================
58// WINDOW SPEC — Client specification for window launch
59// ============================================================================
60
61/// Window spec source — where did this spec come from?
62#[derive(Debug, Clone, PartialEq)]
63pub enum SpecSource {
64    /// Parsed from manifest/config file
65    Manifest(String),
66    /// Provided at runtime via API
67    Runtime,
68    /// Derived from WasmaConfig
69    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/// Decoration style for the window
83#[derive(Debug, Clone, PartialEq)]
84pub enum DecorationStyle {
85    /// Full server-side decorations (title bar, borders)
86    ServerSide,
87    /// Client-side decorations (app draws its own title bar)
88    ClientSide,
89    /// No decorations (borderless)
90    None,
91    /// Auto: let the compositor decide
92    Auto,
93}
94
95/// Window launch specification — complete description of a window to be created
96#[derive(Debug, Clone)]
97pub struct WindowSpec {
98    /// Application identifier
99    pub app_id: String,
100    /// Window title
101    pub title: String,
102    /// Window type (Normal, Dialog, Utility, etc.)
103    pub window_type: WindowType,
104    /// Initial geometry in logical pixels
105    pub geometry: WindowGeometry,
106    /// Decoration style
107    pub decoration: DecorationStyle,
108    /// Initial window state
109    pub initial_state: WindowState,
110    /// Start visible
111    pub start_visible: bool,
112    /// Start focused
113    pub start_focused: bool,
114    /// Parent window ID (for dialogs/popups)
115    pub parent_id: Option<u64>,
116    /// Custom key-value properties (from manifest)
117    pub properties: HashMap<String, String>,
118    /// Spec source
119    pub source: SpecSource,
120    /// Spec creation time
121    pub created_at: SystemTime,
122}
123
124impl WindowSpec {
125    /// Create a minimal spec with defaults
126    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    /// Parse spec from a simple manifest key=value format
164    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    /// Validate spec fields
220    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
242/// WindowSpec builder — chainable
243pub 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// ============================================================================
315// POSIX WINDOW — Active window handle
316// ============================================================================
317
318/// A launched and active POSIX window
319#[derive(Debug, Clone)]
320pub struct PosixWindow {
321    /// Unique window ID
322    pub id: u64,
323    /// The spec used to create this window
324    pub spec: WindowSpec,
325    /// Current geometry (may differ from spec after user resizing)
326    pub geometry: WindowGeometry,
327    /// Current state
328    pub state: WindowState,
329    /// Current visibility
330    pub visible: bool,
331    /// Current focus
332    pub focused: bool,
333    /// Which opt group was applied at launch time
334    pub applied_opt_source: String,
335    /// Launch timestamp
336    pub launched_at: SystemTime,
337    /// Backend used for this window
338    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            // Apply PosixOpt min size constraints
348            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
373// ============================================================================
374// POSIX WINDOW LAUNCHER — Core launch manager
375// ============================================================================
376
377/// PosixWindowLauncher
378///
379/// Manages normal (non-raw) POSIX window creation and lifecycle.
380/// Supports both manifest/config-based and runtime API launch.
381/// Mode: Singularity (one window) or Multitary (many), runtime selectable.
382///
383/// Integrations (all optional except PosixOpt):
384///   - PosixOpt: automatically applied to every launch
385///   - WindowClient: optional rendering delegate
386///   - RawWindowClient: optional low-level delegate
387///   - UClientEngine: implemented for WASMA engine compatibility
388pub struct PosixWindowLauncher {
389    config: Arc<WasmaConfig>,
390
391    /// Option store — applied to every launched window
392    opt_store: Arc<RuntimeOptStore>,
393
394    /// Window mode — singularity or multitary
395    mode: WindowMode,
396
397    /// Active windows
398    windows: Arc<RwLock<HashMap<u64, PosixWindow>>>,
399
400    /// Window ID counter
401    next_id: Arc<Mutex<u64>>,
402
403    /// Optional: WindowClient for rendering
404    window_client: Option<Arc<Mutex<WindowClient>>>,
405
406    /// Optional: RawWindowClient for low-level delegate
407    raw_client: Option<Arc<Mutex<RawWindowClient>>>,
408
409    /// Engine active flag
410    active: bool,
411
412    /// SectionMemory for UClientEngine
413    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    // -------------------------------------------------------------------------
454    // OPTIONAL DELEGATE ATTACHMENT
455    // -------------------------------------------------------------------------
456
457    /// Attach WindowClient for rendering (optional)
458    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    /// Attach RawWindowClient for low-level delegate (optional)
464    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    /// Detach WindowClient
470    pub fn detach_window_client(&mut self) {
471        self.window_client = None;
472        println!("🔌 PosixWindowLauncher: WindowClient detached");
473    }
474
475    /// Detach RawWindowClient
476    pub fn detach_raw_client(&mut self) {
477        self.raw_client = None;
478        println!("🔌 PosixWindowLauncher: RawWindowClient detached");
479    }
480
481    // -------------------------------------------------------------------------
482    // MODE CONTROL
483    // -------------------------------------------------------------------------
484
485    /// Switch window mode at runtime
486    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    // -------------------------------------------------------------------------
500    // OPT OVERRIDE
501    // -------------------------------------------------------------------------
502
503    /// Apply a runtime opt override
504    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    // -------------------------------------------------------------------------
516    // ID ALLOCATION
517    // -------------------------------------------------------------------------
518
519    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    // -------------------------------------------------------------------------
527    // WINDOW LAUNCH — Core
528    // -------------------------------------------------------------------------
529
530    /// Launch a window from a WindowSpec
531    /// In Singularity mode: closes existing window before launching
532    /// In Multitary mode: creates alongside existing windows
533    pub fn launch(&self, spec: WindowSpec) -> Result<u64, String> {
534        // Validate spec
535        spec.validate().map_err(|e| e.join(", "))?;
536
537        // Singularity: close all existing windows first
538        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        // Apply PosixOpt constraints to spec
549        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        // Optional: delegate to RawWindowClient
563        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        // Optional: WindowClient rendering setup
577        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        // Apply toolkit theme from PosixOpt
587        self.apply_toolkit_theme(&opt, id);
588
589        // Store window
590        self.windows.write().unwrap().insert(id, window);
591        Ok(id)
592    }
593
594    /// Launch from WasmaConfig (config-based launch)
595    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    /// Launch from manifest file content (manifest-based launch)
602    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    /// Apply toolkit theme — PosixOpt auto-application
614    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    // -------------------------------------------------------------------------
638    // WINDOW OPERATIONS
639    // -------------------------------------------------------------------------
640
641    /// Close a window by ID
642    pub fn close_window(&self, id: u64) -> Result<(), String> {
643        // Optional: notify RawWindowClient delegate
644        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    /// Close all windows
659    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    /// Set geometry for a window
668    pub fn set_geometry(&self, id: u64, geo: WindowGeometry) -> Result<(), String> {
669        // Delegate to RawWindowClient if attached
670        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                // Notify WindowClient if attached
680                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    /// Set window state
691    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    /// Set window focus
707    pub fn set_focus(&self, id: u64) -> Result<(), String> {
708        // Unfocus all
709        {
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    /// Set window visibility
730    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    /// Set window title
746    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    /// Render a frame to a window
763    /// Routes to WindowClient if attached, otherwise via dispatch_data
764pub 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    // wsdg-open modeli: uygulama kendi render eder, biz dispatch_data ile devam ederiz
777    self.dispatch_data(data);
778    Ok(())
779}
780    // -------------------------------------------------------------------------
781    // ACCESSORS
782    // -------------------------------------------------------------------------
783
784    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
812// ============================================================================
813// UCLIENTENGINE TRAIT IMPLEMENTATION
814// ============================================================================
815
816impl 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        // Auto-launch from config if no windows exist
838        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        // Simple event loop — yield until stopped
847        loop {
848            if !self.active {
849                break;
850            }
851            std::thread::sleep(std::time::Duration::from_millis(16)); // ~60fps tick
852        }
853
854        Ok(())
855    }
856
857    fn dispatch_data(&self, data: &[u8]) {
858        // Dispatch to focused window if any
859        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
888// ============================================================================
889// BUILDER
890// ============================================================================
891
892pub 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// ============================================================================
966// TESTS
967// ============================================================================
968
969#[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); // replaced
1070        launcher.launch(spec3).unwrap();
1071        assert_eq!(launcher.window_count(), 1); // replaced again
1072
1073        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        // Switch to singularity — next launch clears all
1108        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        // Geometry
1124        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        // State
1134        launcher.set_state(id, WindowState::Maximized).unwrap();
1135        assert_eq!(
1136            launcher.get_window(id).unwrap().state,
1137            WindowState::Maximized
1138        );
1139
1140        // Visibility
1141        launcher.set_visible(id, false).unwrap();
1142        assert!(!launcher.get_window(id).unwrap().visible);
1143
1144        // Title
1145        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        // Set min size via opt
1224        launcher.override_opt(|opt| {
1225            opt.draw.size.min_width = 500;
1226            opt.draw.size.min_height = 400;
1227        });
1228
1229        // Launch with smaller geometry
1230        let spec = WindowSpecBuilder::new("small.app", "Small")
1231            .geometry(0, 0, 100, 100) // smaller than min
1232            .build_unchecked();
1233        let id = launcher.launch(spec).unwrap();
1234        let w = launcher.get_window(id).unwrap();
1235        // Should be clamped to min
1236        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        // Invalid width should fall back to default (800)
1258        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}