diff --git a/crates/wrsrvd/src/main.rs b/crates/wrsrvd/src/main.rs index 03fbe7f..7370ef9 100644 --- a/crates/wrsrvd/src/main.rs +++ b/crates/wrsrvd/src/main.rs @@ -2,11 +2,31 @@ mod errors; mod handlers; mod state; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; + +use crate::handlers::ClientState; use crate::state::WayRay; use miette::Result; -use smithay::reexports::wayland_server::Display; +use smithay::{ + backend::{ + renderer::gles::GlesRenderer, + winit::{self, WinitEvent}, + }, + output::{Mode, Output, PhysicalProperties, Subpixel}, + reexports::wayland_server::Display, + utils::Transform, + wayland::{compositor::CompositorClientState, socket::ListeningSocketSource}, +}; use tracing::info; +/// Data accessible from calloop event callbacks. +struct CalloopData { + state: WayRay, + display: Display, +} + fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( @@ -17,10 +37,133 @@ fn main() -> Result<()> { info!("wrsrvd starting"); + // Create the Wayland display. let mut display = Display::::new() .map_err(|e| errors::WayRayError::DisplayInit(Box::new(e)))?; - let _state = WayRay::new(&mut display); - info!("compositor state initialized"); + // Initialize the Winit backend (opens a window, creates a GlesRenderer). + let (backend, winit_event_loop) = winit::init::().map_err(|e| { + errors::WayRayError::BackendInit(Box::::from( + e.to_string(), + )) + })?; + info!("winit backend initialized"); + + // Create a virtual output matching the window size. + let window_size = backend.window_size(); + let output = Output::new( + "wayray-0".to_string(), + PhysicalProperties { + size: (0, 0).into(), + subpixel: Subpixel::Unknown, + make: "WayRay".to_string(), + model: "Virtual".to_string(), + }, + ); + + let mode = Mode { + size: window_size, + refresh: 60_000, // 60 Hz in millihertz + }; + output.change_current_state(Some(mode), Some(Transform::Flipped180), None, None); + output.set_preferred(mode); + + // Create the global output for Wayland clients to bind to. + output.create_global::(&display.handle()); + + // Create compositor state. + let mut state = WayRay::new(&mut display, output.clone()); + + // Map the output into the compositor space. + state.space.map_output(&output, (0, 0)); + info!("output mapped: {:?} @ {:?}", mode.size, mode.refresh); + + // Create a Wayland listening socket for clients. + let listening_socket = ListeningSocketSource::new_auto() + .map_err(|e| errors::WayRayError::DisplayInit(Box::new(e)))?; + let socket_name = listening_socket.socket_name().to_os_string(); + info!(?socket_name, "wayland socket created"); + + // Set WAYLAND_DISPLAY so child processes can find us. + // SAFETY: This is called early in main before any other threads are spawned, + // so there are no concurrent readers of the environment. + unsafe { std::env::set_var("WAYLAND_DISPLAY", &socket_name) }; + + // Create the calloop event loop. + let mut event_loop: smithay::reexports::calloop::EventLoop = + smithay::reexports::calloop::EventLoop::try_new() + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?; + + let loop_handle = event_loop.handle(); + + // Insert the Wayland listening socket as a calloop source. + // When a new client connects, insert it into the display. + loop_handle + .insert_source(listening_socket, |client_stream, _, data| { + data.display + .handle() + .insert_client( + client_stream, + Arc::new(ClientState { + compositor_state: CompositorClientState::default(), + }), + ) + .ok(); + }) + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e.error)))?; + + // Shared flag to signal the main loop to exit. + let running = Arc::new(AtomicBool::new(true)); + let running_clone = running.clone(); + + // Insert the Winit event loop as a calloop source. + loop_handle + .insert_source(winit_event_loop, move |event, _, _data| match event { + WinitEvent::Resized { size, scale_factor } => { + info!(?size, scale_factor, "window resized"); + } + WinitEvent::Focus(focused) => { + info!(focused, "window focus changed"); + } + WinitEvent::Input(_event) => { + // Input handling is Task 7 -- ignore for now. + } + WinitEvent::Redraw => { + // Rendering is Task 6 -- ignore for now. + } + WinitEvent::CloseRequested => { + info!("close requested, shutting down"); + running_clone.store(false, Ordering::SeqCst); + } + }) + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e.error)))?; + + // Keep a handle to the backend (needed for rendering in Task 6). + let _backend = backend; + + let mut calloop_data = CalloopData { state, display }; + + info!("entering main event loop"); + + // Main event loop. + while running.load(Ordering::SeqCst) { + // Dispatch Wayland clients. + calloop_data + .display + .dispatch_clients(&mut calloop_data.state) + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?; + + calloop_data + .display + .flush_clients() + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?; + + // Dispatch calloop sources (Winit events + Wayland socket) with ~16ms timeout. + event_loop + .dispatch(Duration::from_millis(16), &mut calloop_data) + .map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?; + } + + info!("wrsrvd shutting down"); Ok(()) } diff --git a/crates/wrsrvd/src/state.rs b/crates/wrsrvd/src/state.rs index 87c22bd..3bb1596 100644 --- a/crates/wrsrvd/src/state.rs +++ b/crates/wrsrvd/src/state.rs @@ -1,6 +1,7 @@ use smithay::{ desktop::Space, input::{Seat, SeatState}, + output::Output, reexports::wayland_server::{Display, DisplayHandle}, utils::{Clock, Monotonic}, wayland::{ @@ -28,11 +29,12 @@ pub struct WayRay { pub space: Space, pub seat: Seat, pub clock: Clock, + pub output: Output, } impl WayRay { /// Create a new WayRay compositor state, initializing all Smithay subsystems. - pub fn new(display: &mut Display) -> Self { + pub fn new(display: &mut Display, output: Output) -> Self { let dh = display.handle(); let compositor_state = CompositorState::new::(&dh); @@ -58,6 +60,7 @@ impl WayRay { space: Space::default(), seat, clock: Clock::new(), + output, } } }