// SPDX-License-Identifier: GPL-3.0-only use crate::{ backend::render, config::OutputConfig, input::Devices, state::{BackendData, Common, Data}, utils::prelude::*, wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ egl::EGLDevice, renderer::{ damage::{OutputDamageTracker, RenderOutputResult}, gles::GlesRenderbuffer, glow::GlowRenderer, ImportDma, ImportEgl, }, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::{ calloop::{ping, EventLoop}, wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, }, utils::Transform, wayland::dmabuf::DmabufFeedbackBuilder, }; use std::cell::RefCell; use tracing::{error, info, warn}; #[cfg(feature = "debug")] use crate::state::Fps; use super::render::{init_shaders, CursorMode}; #[derive(Debug)] pub struct WinitState { // The winit backend currently has no notion of multiple windows pub backend: WinitGraphicsBackend, output: Output, damage_tracker: OutputDamageTracker, screencopy: Vec<(ScreencopySession, BufferParams)>, #[cfg(feature = "debug")] fps: Fps, } impl WinitState { pub fn render_output(&mut self, state: &mut Common) -> Result<()> { self.backend .bind() .with_context(|| "Failed to bind buffer")?; let age = self.backend.buffer_age().unwrap_or(0); let surface = self.backend.egl_surface(); match render::render_output::<_, _, GlesRenderbuffer, _>( None, self.backend.renderer(), surface.clone(), &mut self.damage_tracker, age, state, &self.output, CursorMode::NotDefault, if !self.screencopy.is_empty() { Some((surface, &self.screencopy)) } else { None }, #[cfg(not(feature = "debug"))] None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { Ok(RenderOutputResult { damage, states, .. }) => { self.backend .bind() .with_context(|| "Failed to bind display")?; self.backend .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; self.screencopy.clear(); #[cfg(feature = "debug")] self.fps.displayed(); state.send_frames(&self.output, &states, |_| None); if damage.is_some() { let mut output_presentation_feedback = state.take_presentation_feedback(&self.output, &states); output_presentation_feedback.presented( state.clock.now(), self.output .current_mode() .map(|mode| mode.refresh as u32) .unwrap_or_default(), 0, wp_presentation_feedback::Kind::Vsync, ); } } Err(err) => { for (session, params) in self.screencopy.drain(..) { state.still_pending(session, params) } anyhow::bail!("Rendering failed: {}", err); } }; Ok(()) } pub fn apply_config_for_output( &mut self, output: &Output, test_only: bool, ) -> Result<(), anyhow::Error> { // TODO: if we ever have multiple winit outputs, don't ignore config.enabled // reset size let size = self.backend.window_size(); let mut config = output .user_data() .get::>() .unwrap() .borrow_mut(); if dbg!(config.mode.0) != dbg!((size.physical_size.w as i32, size.physical_size.h as i32)) { if !test_only { config.mode = ( (size.physical_size.w as i32, size.physical_size.h as i32), None, ); } Err(anyhow::anyhow!("Cannot set window size")) } else { Ok(()) } } pub fn pending_screencopy(&mut self, new: Option>) { if let Some(sessions) = new { self.screencopy.extend(sessions); } } } pub fn init_backend( dh: &DisplayHandle, event_loop: &mut EventLoop, state: &mut State, ) -> Result<()> { let (mut backend, mut input) = winit::init().map_err(|_| anyhow!("Failed to initilize winit backend"))?; init_shaders(backend.renderer()).expect("Failed to initialize renderer"); init_egl_client_side(dh, state, &mut backend)?; let name = format!("WINIT-0"); let size = backend.window_size(); let props = PhysicalProperties { size: (0, 0).into(), subpixel: Subpixel::Unknown, make: "COSMIC".to_string(), model: name.clone(), }; let mode = Mode { size: (size.physical_size.w as i32, size.physical_size.h as i32).into(), refresh: 60_000, }; let output = Output::new(name, props); output.add_mode(mode); output.set_preferred(mode); output.change_current_state( Some(mode), Some(Transform::Flipped180), Some(Scale::Integer(1)), Some((0, 0).into()), ); output.user_data().insert_if_missing(|| { RefCell::new(OutputConfig { mode: ( (size.physical_size.w as i32, size.physical_size.h as i32), None, ), transform: Transform::Flipped180.into(), ..Default::default() }) }); let (event_ping, event_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; let (render_ping, render_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; let event_ping_handle = event_ping.clone(); let render_ping_handle = render_ping.clone(); let mut token = Some( event_loop .handle() .insert_source(render_source, move |_, _, data| { if let Err(err) = data .state .backend .winit() .render_output(&mut data.state.common) { error!(?err, "Failed to render frame."); render_ping.ping(); } }) .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?, ); let event_loop_handle = event_loop.handle(); event_loop .handle() .insert_source(event_source, move |_, _, data| { match input.dispatch_new_events(|event| { data.state.process_winit_event(event, &render_ping_handle) }) { Ok(_) => { event_ping_handle.ping(); render_ping_handle.ping(); } Err(winit::WinitError::WindowClosed) => { let output = data.state.backend.winit().output.clone(); let seats = data.state.common.seats().cloned().collect::>(); data.state .common .shell .remove_output(&output, seats.into_iter()); if let Some(token) = token.take() { event_loop_handle.remove(token); } } }; }) .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?; event_ping.ping(); #[cfg(feature = "debug")] let fps = Fps::new(backend.renderer()); state.backend = BackendData::Winit(WinitState { backend, output: output.clone(), damage_tracker: OutputDamageTracker::from_output(&output), screencopy: Vec::new(), #[cfg(feature = "debug")] fps, }); state .common .output_configuration_state .add_heads(std::iter::once(&output)); state.common.shell.add_output(&output); let seats = state.common.seats().cloned().collect::>(); state.common.config.read_outputs( &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, seats.iter().cloned(), &state.common.event_loop_handle, ); state.launch_xwayland(None); Ok(()) } fn init_egl_client_side( dh: &DisplayHandle, state: &mut State, renderer: &mut WinitGraphicsBackend, ) -> Result<()> { let bind_result = renderer.renderer().bind_wl_display(dh); let render_node = EGLDevice::device_for_display(renderer.renderer().egl_context().display()) .and_then(|device| device.try_get_render_node()); let dmabuf_formats = renderer.renderer().dmabuf_formats().collect::>(); let dmabuf_default_feedback = match render_node { Ok(Some(node)) => { let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone()) .build() .unwrap(); Some(dmabuf_default_feedback) } Ok(None) => { warn!("Failed to query render node, dmabuf protocol will only advertise v3"); None } Err(err) => { warn!( ?err, "Failed to egl device for display, dmabuf protocol will only advertise v3" ); None } }; match dmabuf_default_feedback { Some(feedback) => { state .common .dmabuf_state .create_global_with_default_feedback::(dh, &feedback); info!("EGL hardware-acceleration enabled."); } None if bind_result.is_ok() => { state .common .dmabuf_state .create_global::(dh, dmabuf_formats); info!("EGL hardware-acceleration enabled."); } None => { let err = bind_result.unwrap_err(); warn!(?err, "Unable to initialize bind display to EGL.") } } Ok(()) } impl State { pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) { // here we can handle special cases for winit inputs match event { WinitEvent::Focus(true) => { for seat in self.common.seats().cloned().collect::>().iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&WinitVirtualDevice) { seat.set_active_output(&self.backend.winit().output); break; } } } WinitEvent::Resized { size, .. } => { let winit_state = self.backend.winit(); let output = &winit_state.output; let mode = Mode { size, refresh: 60_000, }; { let mut config = output .user_data() .get::>() .unwrap() .borrow_mut(); config.mode.0 = size.into(); } output.delete_mode(output.current_mode().unwrap()); output.set_preferred(mode); output.change_current_state(Some(mode), None, None, None); layer_map_for_output(output).arrange(); self.common.output_configuration_state.update(); self.common.shell.refresh_outputs(); render_ping.ping(); } WinitEvent::Refresh => render_ping.ping(), WinitEvent::Input(event) => self.process_input_event(event, false), _ => {} }; } }