From a8f581a506e65759117f3cc8ae35b5ad76fd39c8 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sun, 3 Sep 2023 13:07:16 +0400 Subject: Move backends to subfolder --- src/backend.rs | 63 ------ src/backend/mod.rs | 67 ++++++ src/backend/tty.rs | 629 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/backend/winit.rs | 178 +++++++++++++++ src/main.rs | 6 +- src/tty.rs | 629 --------------------------------------------------- src/winit.rs | 178 --------------- 7 files changed, 875 insertions(+), 875 deletions(-) delete mode 100644 src/backend.rs create mode 100644 src/backend/mod.rs create mode 100644 src/backend/tty.rs create mode 100644 src/backend/winit.rs delete mode 100644 src/tty.rs delete mode 100644 src/winit.rs diff --git a/src/backend.rs b/src/backend.rs deleted file mode 100644 index 12f5a0a3..00000000 --- a/src/backend.rs +++ /dev/null @@ -1,63 +0,0 @@ -use smithay::backend::renderer::gles::GlesRenderer; -use smithay::output::Output; - -use crate::niri::OutputRenderElements; -use crate::tty::Tty; -use crate::winit::Winit; -use crate::Niri; - -pub enum Backend { - Tty(Tty), - Winit(Winit), -} - -impl Backend { - pub fn init(&mut self, niri: &mut Niri) { - match self { - Backend::Tty(tty) => tty.init(niri), - Backend::Winit(winit) => winit.init(niri), - } - } - - pub fn seat_name(&self) -> String { - match self { - Backend::Tty(tty) => tty.seat_name(), - Backend::Winit(winit) => winit.seat_name(), - } - } - - pub fn renderer(&mut self) -> &mut GlesRenderer { - match self { - Backend::Tty(tty) => tty.renderer(), - Backend::Winit(winit) => winit.renderer(), - } - } - - pub fn render( - &mut self, - niri: &mut Niri, - output: &Output, - elements: &[OutputRenderElements], - ) { - match self { - Backend::Tty(tty) => tty.render(niri, output, elements), - Backend::Winit(winit) => winit.render(niri, output, elements), - } - } - - pub fn tty(&mut self) -> Option<&mut Tty> { - if let Self::Tty(v) = self { - Some(v) - } else { - None - } - } - - pub fn winit(&mut self) -> Option<&mut Winit> { - if let Self::Winit(v) = self { - Some(v) - } else { - None - } - } -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 00000000..5c0f62a5 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,67 @@ +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::output::Output; + +use crate::niri::OutputRenderElements; +use crate::Niri; + +pub mod tty; +pub use tty::Tty; + +pub mod winit; +pub use winit::Winit; + +pub enum Backend { + Tty(Tty), + Winit(Winit), +} + +impl Backend { + pub fn init(&mut self, niri: &mut Niri) { + match self { + Backend::Tty(tty) => tty.init(niri), + Backend::Winit(winit) => winit.init(niri), + } + } + + pub fn seat_name(&self) -> String { + match self { + Backend::Tty(tty) => tty.seat_name(), + Backend::Winit(winit) => winit.seat_name(), + } + } + + pub fn renderer(&mut self) -> &mut GlesRenderer { + match self { + Backend::Tty(tty) => tty.renderer(), + Backend::Winit(winit) => winit.renderer(), + } + } + + pub fn render( + &mut self, + niri: &mut Niri, + output: &Output, + elements: &[OutputRenderElements], + ) { + match self { + Backend::Tty(tty) => tty.render(niri, output, elements), + Backend::Winit(winit) => winit.render(niri, output, elements), + } + } + + pub fn tty(&mut self) -> Option<&mut Tty> { + if let Self::Tty(v) = self { + Some(v) + } else { + None + } + } + + pub fn winit(&mut self) -> Option<&mut Winit> { + if let Self::Winit(v) = self { + Some(v) + } else { + None + } + } +} diff --git a/src/backend/tty.rs b/src/backend/tty.rs new file mode 100644 index 00000000..63256821 --- /dev/null +++ b/src/backend/tty.rs @@ -0,0 +1,629 @@ +use std::collections::{HashMap, HashSet}; +use std::os::fd::FromRawFd; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use anyhow::{anyhow, Context}; +use smithay::backend::allocator::dmabuf::Dmabuf; +use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; +use smithay::backend::allocator::{Format as DrmFormat, Fourcc}; +use smithay::backend::drm::compositor::DrmCompositor; +use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime}; +use smithay::backend::egl::{EGLContext, EGLDisplay}; +use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface}; +use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; +use smithay::backend::renderer::{Bind, DebugFlags, ImportEgl}; +use smithay::backend::session::libseat::LibSeatSession; +use smithay::backend::session::{Event as SessionEvent, Session}; +use smithay::backend::udev::{self, UdevBackend, UdevEvent}; +use smithay::desktop::utils::OutputPresentationFeedback; +use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel}; +use smithay::reexports::calloop::{Dispatcher, LoopHandle, RegistrationToken}; +use smithay::reexports::drm::control::{ + connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags, +}; +use smithay::reexports::input::Libinput; +use smithay::reexports::nix::fcntl::OFlag; +use smithay::reexports::nix::libc::dev_t; +use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; +use smithay::utils::DeviceFd; +use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner}; +use smithay_drm_extras::edid::EdidInfo; + +use crate::input::{BackendAction, CompositorMod}; +use crate::niri::OutputRenderElements; +use crate::{LoopData, Niri}; + +const BACKGROUND_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.]; +const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888]; + +pub struct Tty { + session: LibSeatSession, + udev_dispatcher: Dispatcher<'static, UdevBackend, LoopData>, + primary_gpu_path: PathBuf, + output_device: Option, +} + +type GbmDrmCompositor = DrmCompositor< + GbmAllocator, + GbmDevice, + OutputPresentationFeedback, + DrmDeviceFd, +>; + +struct OutputDevice { + id: dev_t, + token: RegistrationToken, + drm: DrmDevice, + gbm: GbmDevice, + gles: GlesRenderer, + formats: HashSet, + drm_scanner: DrmScanner, + surfaces: HashMap, +} + +#[derive(Debug, Clone, Copy)] +struct TtyOutputState { + device_id: dev_t, + crtc: crtc::Handle, +} + +impl Tty { + pub fn new(event_loop: LoopHandle<'static, LoopData>) -> Self { + let (session, notifier) = LibSeatSession::new().unwrap(); + let seat_name = session.seat(); + + let udev_backend = UdevBackend::new(session.seat()).unwrap(); + let udev_dispatcher = + Dispatcher::new(udev_backend, move |event, _, data: &mut LoopData| { + let tty = data.backend.tty().unwrap(); + let niri = &mut data.niri; + + match event { + UdevEvent::Added { device_id, path } => { + if !tty.session.is_active() { + debug!("skipping UdevEvent::Added as session is inactive"); + return; + } + + if let Err(err) = tty.device_added(device_id, &path, niri) { + warn!("error adding device: {err:?}"); + } + } + UdevEvent::Changed { device_id } => { + if !tty.session.is_active() { + debug!("skipping UdevEvent::Changed as session is inactive"); + return; + } + + tty.device_changed(device_id, niri) + } + UdevEvent::Removed { device_id } => { + if !tty.session.is_active() { + debug!("skipping UdevEvent::Removed as session is inactive"); + return; + } + + tty.device_removed(device_id, niri) + } + } + }); + event_loop + .register_dispatcher(udev_dispatcher.clone()) + .unwrap(); + + let mut libinput = Libinput::new_with_udev(LibinputSessionInterface::from(session.clone())); + libinput.udev_assign_seat(&seat_name).unwrap(); + + let input_backend = LibinputInputBackend::new(libinput.clone()); + event_loop + .insert_source(input_backend, |mut event, _, data| { + let tty = data.backend.tty().unwrap(); + let niri = &mut data.niri; + + niri.process_libinput_event(&mut event); + match niri.process_input_event(CompositorMod::Super, event) { + BackendAction::None => (), + BackendAction::ChangeVt(vt) => tty.change_vt(vt), + BackendAction::Suspend => { + if let Err(err) = suspend() { + warn!("error suspending: {err:?}"); + } + } + BackendAction::Screenshot => { + let active = niri.monitor_set.active_output().cloned(); + if let Some(active) = active { + if let Err(err) = niri.screenshot(tty.renderer(), &active) { + warn!("error taking screenshot: {err:?}"); + } + } + } + BackendAction::ToggleDebugTint => { + if let Some(device) = tty.output_device.as_mut() { + for (_, compositor) in &mut device.surfaces { + compositor + .set_debug_flags(compositor.debug_flags() ^ DebugFlags::TINT); + } + } + } + }; + }) + .unwrap(); + + let udev_dispatcher_c = udev_dispatcher.clone(); + event_loop + .insert_source(notifier, move |event, _, data| { + let tty = data.backend.tty().unwrap(); + let niri = &mut data.niri; + + match event { + SessionEvent::PauseSession => { + debug!("pausing session"); + + libinput.suspend(); + + if let Some(output_device) = &tty.output_device { + output_device.drm.pause(); + } + } + SessionEvent::ActivateSession => { + debug!("resuming session"); + + if libinput.resume().is_err() { + error!("error resuming libinput"); + } + + if let Some(output_device) = &mut tty.output_device { + // We had an output device, check if it's been removed. + let output_device_id = output_device.id; + if !udev_dispatcher_c + .as_source_ref() + .device_list() + .any(|(device_id, _)| device_id == output_device_id) + { + // The output device, if we had any, has been removed. + tty.device_removed(output_device_id, niri); + } else { + // It hasn't been removed, update its state as usual. + output_device.drm.activate(); + + // HACK: force reset the connectors to make resuming work across + // sleep. + let output_device = tty.output_device.as_mut().unwrap(); + let crtcs: Vec<_> = output_device + .drm_scanner + .crtcs() + .map(|(conn, crtc)| (conn.clone(), crtc)) + .collect(); + for (conn, crtc) in crtcs { + tty.connector_disconnected(niri, conn, crtc); + } + + let output_device = tty.output_device.as_mut().unwrap(); + let _ = output_device + .drm_scanner + .scan_connectors(&output_device.drm); + let crtcs: Vec<_> = output_device + .drm_scanner + .crtcs() + .map(|(conn, crtc)| (conn.clone(), crtc)) + .collect(); + for (conn, crtc) in crtcs { + if let Err(err) = tty.connector_connected(niri, conn, crtc) { + warn!("error connecting connector: {err:?}"); + } + } + + // // Refresh the connectors. + // tty.device_changed(output_device_id, niri); + + // // Refresh the state on unchanged connectors. + // let output_device = tty.output_device.as_mut().unwrap(); + // for drm_compositor in output_device.surfaces.values_mut() { + // if let Err(err) = drm_compositor.surface().reset_state() { + // warn!("error resetting DRM surface state: {err}"); + // } + // drm_compositor.reset_buffers(); + // } + + // niri.queue_redraw_all(); + } + } else { + // We didn't have an output device, check if it's been added. + for (device_id, path) in udev_dispatcher_c.as_source_ref().device_list() + { + if let Err(err) = tty.device_added(device_id, path, niri) { + warn!("error adding device: {err:?}"); + } + } + } + } + } + }) + .unwrap(); + + let primary_gpu_path = udev::primary_gpu(&seat_name).unwrap().unwrap(); + + Self { + session, + udev_dispatcher, + primary_gpu_path, + output_device: None, + } + } + + pub fn init(&mut self, niri: &mut Niri) { + for (device_id, path) in self.udev_dispatcher.clone().as_source_ref().device_list() { + if let Err(err) = self.device_added(device_id, path, niri) { + warn!("error adding device: {err:?}"); + } + } + } + + fn device_added( + &mut self, + device_id: dev_t, + path: &Path, + niri: &mut Niri, + ) -> anyhow::Result<()> { + if path != self.primary_gpu_path { + debug!("skipping non-primary device {path:?}"); + return Ok(()); + } + + debug!("adding device {path:?}"); + assert!(self.output_device.is_none()); + + let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK; + let fd = self.session.open(path, open_flags)?; + let device_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(fd)) }; + + let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?; + let gbm = GbmDevice::new(device_fd)?; + + let display = EGLDisplay::new(gbm.clone())?; + let egl_context = EGLContext::new(&display)?; + + let mut gles = unsafe { GlesRenderer::new(egl_context)? }; + gles.bind_wl_display(&niri.display_handle)?; + + let token = niri + .event_loop + .insert_source(drm_notifier, move |event, metadata, data| { + let tty = data.backend.tty().unwrap(); + match event { + DrmEvent::VBlank(crtc) => { + tracy_client::Client::running() + .unwrap() + .message("vblank", 0); + trace!("vblank {metadata:?}"); + + let device = tty.output_device.as_mut().unwrap(); + let drm_compositor = device.surfaces.get_mut(&crtc).unwrap(); + + let presentation_time = match metadata.as_mut().unwrap().time { + DrmEventTime::Monotonic(time) => time, + DrmEventTime::Realtime(_) => { + // Not supported. + + // This value will be ignored in the frame clock code. + Duration::ZERO + } + }; + + // Mark the last frame as submitted. + match drm_compositor.frame_submitted() { + Ok(Some(mut feedback)) => { + let refresh = + feedback.output().unwrap().current_mode().unwrap().refresh + as u32; + // FIXME: ideally should be monotonically increasing for a surface. + let seq = metadata.as_ref().unwrap().sequence as u64; + let flags = wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwClock + | wp_presentation_feedback::Kind::HwCompletion; + + feedback.presented::<_, smithay::utils::Monotonic>( + presentation_time, + refresh, + seq, + flags, + ); + } + Ok(None) => (), + Err(err) => { + error!("error marking frame as submitted: {err}"); + } + } + + let output = data + .niri + .global_space + .outputs() + .find(|output| { + let tty_state: &TtyOutputState = output.user_data().get().unwrap(); + tty_state.device_id == device.id && tty_state.crtc == crtc + }) + .unwrap() + .clone(); + let output_state = data.niri.output_state.get_mut(&output).unwrap(); + output_state.waiting_for_vblank = false; + output_state.frame_clock.presented(presentation_time); + data.niri.queue_redraw(output); + } + DrmEvent::Error(error) => error!("DRM error: {error}"), + }; + }) + .unwrap(); + + let formats = Bind::::supported_formats(&gles).unwrap_or_default(); + + self.output_device = Some(OutputDevice { + id: device_id, + token, + drm, + gbm, + gles, + formats, + drm_scanner: DrmScanner::new(), + surfaces: HashMap::new(), + }); + + self.device_changed(device_id, niri); + + Ok(()) + } + + fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) { + let Some(device) = &mut self.output_device else { + return; + }; + if device.id != device_id { + return; + } + debug!("output device changed"); + + for event in device.drm_scanner.scan_connectors(&device.drm) { + match event { + DrmScanEvent::Connected { + connector, + crtc: Some(crtc), + } => { + if let Err(err) = self.connector_connected(niri, connector, crtc) { + warn!("error connecting connector: {err:?}"); + } + } + DrmScanEvent::Disconnected { + connector, + crtc: Some(crtc), + } => self.connector_disconnected(niri, connector, crtc), + _ => (), + } + } + } + + fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) { + let Some(device) = &mut self.output_device else { + return; + }; + if device_id != device.id { + return; + } + + let crtcs: Vec<_> = device + .drm_scanner + .crtcs() + .map(|(info, crtc)| (info.clone(), crtc)) + .collect(); + + for (connector, crtc) in crtcs { + self.connector_disconnected(niri, connector, crtc); + } + + let mut device = self.output_device.take().unwrap(); + device.gles.unbind_wl_display(); + niri.event_loop.remove(device.token); + } + + fn connector_connected( + &mut self, + niri: &mut Niri, + connector: connector::Info, + crtc: crtc::Handle, + ) -> anyhow::Result<()> { + let output_name = format!( + "{}-{}", + connector.interface().as_str(), + connector.interface_id(), + ); + debug!("connecting connector: {output_name}"); + + let device = self.output_device.as_mut().unwrap(); + + let mut mode = connector.modes().get(0); + connector.modes().iter().for_each(|m| { + debug!("mode: {m:?}"); + + if m.mode_type().contains(ModeTypeFlags::PREFERRED) { + // Pick the highest refresh rate. + if mode + .map(|curr| curr.vrefresh() < m.vrefresh()) + .unwrap_or(true) + { + mode = Some(m); + } + } + }); + let mode = mode.ok_or_else(|| anyhow!("no mode"))?; + debug!("picking mode: {mode:?}"); + + let surface = device + .drm + .create_surface(crtc, *mode, &[connector.handle()])?; + + // Create GBM allocator. + let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT; + let allocator = GbmAllocator::new(device.gbm.clone(), gbm_flags); + + // Update the output mode. + let (physical_width, physical_height) = connector.size().unwrap_or((0, 0)); + + let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle()) + .map(|info| (info.manufacturer, info.model)) + .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); + + let output = Output::new( + output_name, + PhysicalProperties { + size: (physical_width as i32, physical_height as i32).into(), + subpixel: Subpixel::Unknown, + model, + make, + }, + ); + let wl_mode = Mode::from(*mode); + output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into())); + output.set_preferred(wl_mode); + + output.user_data().insert_if_missing(|| TtyOutputState { + device_id: device.id, + crtc, + }); + + // Create the compositor. + let compositor = DrmCompositor::new( + OutputModeSource::Auto(output.clone()), + surface, + None, + allocator, + device.gbm.clone(), + SUPPORTED_COLOR_FORMATS, + device.formats.clone(), + device.drm.cursor_size(), + Some(device.gbm.clone()), + )?; + + let res = device.surfaces.insert(crtc, compositor); + assert!(res.is_none(), "crtc must not have already existed"); + + niri.add_output(output.clone(), Some(refresh_interval(*mode))); + niri.queue_redraw(output); + + Ok(()) + } + + fn connector_disconnected( + &mut self, + niri: &mut Niri, + connector: connector::Info, + crtc: crtc::Handle, + ) { + debug!("disconnecting connector: {connector:?}"); + let device = self.output_device.as_mut().unwrap(); + + if device.surfaces.remove(&crtc).is_none() { + debug!("crts wasn't enabled"); + return; + } + + let output = niri + .global_space + .outputs() + .find(|output| { + let tty_state: &TtyOutputState = output.user_data().get().unwrap(); + tty_state.device_id == device.id && tty_state.crtc == crtc + }) + .unwrap() + .clone(); + + niri.remove_output(&output); + } + + pub fn seat_name(&self) -> String { + self.session.seat() + } + + pub fn renderer(&mut self) -> &mut GlesRenderer { + &mut self.output_device.as_mut().unwrap().gles + } + + pub fn render( + &mut self, + niri: &mut Niri, + output: &Output, + elements: &[OutputRenderElements], + ) { + let _span = tracy_client::span!("Tty::render"); + + let device = self.output_device.as_mut().unwrap(); + let tty_state: &TtyOutputState = output.user_data().get().unwrap(); + let drm_compositor = device.surfaces.get_mut(&tty_state.crtc).unwrap(); + + match drm_compositor.render_frame::<_, _, GlesTexture>( + &mut device.gles, + elements, + BACKGROUND_COLOR, + ) { + Ok(res) => { + assert!(!res.needs_sync()); + if res.damage.is_some() { + let presentation_feedbacks = + niri.take_presentation_feedbacks(output, &res.states); + + match drm_compositor.queue_frame(presentation_feedbacks) { + Ok(()) => { + niri.output_state + .get_mut(output) + .unwrap() + .waiting_for_vblank = true + } + Err(err) => { + error!("error queueing frame: {err}"); + } + } + } + } + Err(err) => { + // Can fail if we switched to a different TTY. + error!("error rendering frame: {err}"); + } + } + } + + fn change_vt(&mut self, vt: i32) { + if let Err(err) = self.session.change_vt(vt) { + error!("error changing VT: {err}"); + } + } +} + +fn refresh_interval(mode: DrmMode) -> Duration { + let clock = mode.clock() as u64; + let htotal = mode.hsync().2 as u64; + let vtotal = mode.vsync().2 as u64; + + let mut numerator = htotal * vtotal * 1_000_000; + let mut denominator = clock; + + if mode.flags().contains(ModeFlags::INTERLACE) { + denominator *= 2; + } + + if mode.flags().contains(ModeFlags::DBLSCAN) { + numerator *= 2; + } + + if mode.vscan() > 1 { + numerator *= mode.vscan() as u64; + } + + let refresh_interval = (numerator + denominator / 2) / denominator; + Duration::from_nanos(refresh_interval) +} + +fn suspend() -> anyhow::Result<()> { + let conn = zbus::blocking::Connection::system().context("error connecting to system bus")?; + let manager = logind_zbus::manager::ManagerProxyBlocking::new(&conn) + .context("error creating login manager proxy")?; + manager.suspend(true).context("error suspending") +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs new file mode 100644 index 00000000..35bf3407 --- /dev/null +++ b/src/backend/winit.rs @@ -0,0 +1,178 @@ +use std::time::Duration; + +use smithay::backend::renderer::damage::OutputDamageTracker; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::backend::renderer::{DebugFlags, Renderer}; +use smithay::backend::winit::{self, WinitError, WinitEvent, WinitEventLoop, WinitGraphicsBackend}; +use smithay::output::{Mode, Output, PhysicalProperties, Subpixel}; +use smithay::reexports::calloop::timer::{TimeoutAction, Timer}; +use smithay::reexports::calloop::LoopHandle; +use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; +use smithay::reexports::winit::dpi::LogicalSize; +use smithay::reexports::winit::window::WindowBuilder; +use smithay::utils::Transform; + +use crate::input::{BackendAction, CompositorMod}; +use crate::niri::OutputRenderElements; +use crate::utils::get_monotonic_time; +use crate::{LoopData, Niri}; + +pub struct Winit { + output: Output, + backend: WinitGraphicsBackend, + winit_event_loop: WinitEventLoop, + damage_tracker: OutputDamageTracker, +} + +impl Winit { + pub fn new(event_loop: LoopHandle) -> Self { + let builder = WindowBuilder::new() + .with_inner_size(LogicalSize::new(1280.0, 800.0)) + // .with_resizable(false) + .with_title("niri"); + let (backend, winit_event_loop) = winit::init_from_builder(builder).unwrap(); + + let mode = Mode { + size: backend.window_size().physical_size, + refresh: 60_000, + }; + + let output = Output::new( + "winit".to_string(), + PhysicalProperties { + size: (0, 0).into(), + subpixel: Subpixel::Unknown, + make: "Smithay".into(), + model: "Winit".into(), + }, + ); + output.change_current_state( + Some(mode), + Some(Transform::Flipped180), + None, + Some((0, 0).into()), + ); + output.set_preferred(mode); + + let damage_tracker = OutputDamageTracker::from_output(&output); + + let timer = Timer::immediate(); + event_loop + .insert_source(timer, move |_, _, data| { + let winit = data.backend.winit().unwrap(); + winit.dispatch(&mut data.niri); + TimeoutAction::ToDuration(Duration::from_micros(16667)) + }) + .unwrap(); + + Self { + output, + backend, + winit_event_loop, + damage_tracker, + } + } + + pub fn init(&mut self, niri: &mut Niri) { + // For some reason, binding the display here causes damage tracker artifacts. + // + // use smithay::backend::renderer::ImportEgl; + // + // if let Err(err) = self + // .backend + // .renderer() + // .bind_wl_display(&niri.display_handle) + // { + // warn!("error binding renderer wl_display: {err}"); + // } + niri.add_output(self.output.clone(), None); + } + + fn dispatch(&mut self, niri: &mut Niri) { + let renderer = self.backend.renderer(); + let res = self + .winit_event_loop + .dispatch_new_events(|event| match event { + WinitEvent::Resized { size, .. } => { + self.output.change_current_state( + Some(Mode { + size, + refresh: 60_000, + }), + None, + None, + None, + ); + niri.output_resized(self.output.clone()); + } + WinitEvent::Input(event) => { + match niri.process_input_event(CompositorMod::Alt, event) { + BackendAction::None => (), + BackendAction::ChangeVt(_) => (), + BackendAction::Suspend => (), + BackendAction::Screenshot => { + let active = niri.monitor_set.active_output().cloned(); + if let Some(active) = active { + if let Err(err) = niri.screenshot(renderer, &active) { + warn!("error taking screenshot: {err:?}"); + } + } + } + BackendAction::ToggleDebugTint => { + renderer.set_debug_flags(renderer.debug_flags() ^ DebugFlags::TINT); + } + } + } + WinitEvent::Focus(_) => (), + WinitEvent::Refresh => niri.queue_redraw(self.output.clone()), + }); + + // I want this to stop compiling if more errors are added. + #[allow(clippy::single_match)] + match res { + Err(WinitError::WindowClosed) => { + niri.stop_signal.stop(); + niri.remove_output(&self.output); + } + Ok(()) => (), + } + } + + pub fn seat_name(&self) -> String { + "winit".to_owned() + } + + pub fn renderer(&mut self) -> &mut GlesRenderer { + self.backend.renderer() + } + + pub fn render( + &mut self, + niri: &mut Niri, + output: &Output, + elements: &[OutputRenderElements], + ) { + let _span = tracy_client::span!("Winit::render"); + + self.backend.bind().unwrap(); + let age = self.backend.buffer_age().unwrap(); + let res = self + .damage_tracker + .render_output(self.backend.renderer(), age, elements, [0.1, 0.1, 0.1, 1.0]) + .unwrap(); + if let Some(damage) = res.damage { + self.backend.submit(Some(&damage)).unwrap(); + + let mut presentation_feedbacks = niri.take_presentation_feedbacks(output, &res.states); + let refresh = output.current_mode().unwrap().refresh as u32; + presentation_feedbacks.presented::<_, smithay::utils::Monotonic>( + get_monotonic_time(), + refresh, + 0, + wp_presentation_feedback::Kind::empty(), + ); + + self.backend.window().request_redraw(); + } + } +} diff --git a/src/main.rs b/src/main.rs index 216dedee..222aa1e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,21 +10,17 @@ mod frame_clock; mod input; mod layout; mod niri; -mod tty; mod utils; -mod winit; use std::env; use std::ffi::OsString; -use backend::Backend; +use backend::{Backend, Tty, Winit}; use clap::Parser; use niri::Niri; use smithay::reexports::calloop::EventLoop; use smithay::reexports::wayland_server::{Display, DisplayHandle}; use tracing_subscriber::EnvFilter; -use tty::Tty; -use winit::Winit; #[derive(Parser)] #[command(author, version, about, long_about = None)] diff --git a/src/tty.rs b/src/tty.rs deleted file mode 100644 index 63256821..00000000 --- a/src/tty.rs +++ /dev/null @@ -1,629 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::os::fd::FromRawFd; -use std::path::{Path, PathBuf}; -use std::time::Duration; - -use anyhow::{anyhow, Context}; -use smithay::backend::allocator::dmabuf::Dmabuf; -use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; -use smithay::backend::allocator::{Format as DrmFormat, Fourcc}; -use smithay::backend::drm::compositor::DrmCompositor; -use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime}; -use smithay::backend::egl::{EGLContext, EGLDisplay}; -use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface}; -use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; -use smithay::backend::renderer::{Bind, DebugFlags, ImportEgl}; -use smithay::backend::session::libseat::LibSeatSession; -use smithay::backend::session::{Event as SessionEvent, Session}; -use smithay::backend::udev::{self, UdevBackend, UdevEvent}; -use smithay::desktop::utils::OutputPresentationFeedback; -use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel}; -use smithay::reexports::calloop::{Dispatcher, LoopHandle, RegistrationToken}; -use smithay::reexports::drm::control::{ - connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags, -}; -use smithay::reexports::input::Libinput; -use smithay::reexports::nix::fcntl::OFlag; -use smithay::reexports::nix::libc::dev_t; -use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; -use smithay::utils::DeviceFd; -use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner}; -use smithay_drm_extras::edid::EdidInfo; - -use crate::input::{BackendAction, CompositorMod}; -use crate::niri::OutputRenderElements; -use crate::{LoopData, Niri}; - -const BACKGROUND_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.]; -const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888]; - -pub struct Tty { - session: LibSeatSession, - udev_dispatcher: Dispatcher<'static, UdevBackend, LoopData>, - primary_gpu_path: PathBuf, - output_device: Option, -} - -type GbmDrmCompositor = DrmCompositor< - GbmAllocator, - GbmDevice, - OutputPresentationFeedback, - DrmDeviceFd, ->; - -struct OutputDevice { - id: dev_t, - token: RegistrationToken, - drm: DrmDevice, - gbm: GbmDevice, - gles: GlesRenderer, - formats: HashSet, - drm_scanner: DrmScanner, - surfaces: HashMap, -} - -#[derive(Debug, Clone, Copy)] -struct TtyOutputState { - device_id: dev_t, - crtc: crtc::Handle, -} - -impl Tty { - pub fn new(event_loop: LoopHandle<'static, LoopData>) -> Self { - let (session, notifier) = LibSeatSession::new().unwrap(); - let seat_name = session.seat(); - - let udev_backend = UdevBackend::new(session.seat()).unwrap(); - let udev_dispatcher = - Dispatcher::new(udev_backend, move |event, _, data: &mut LoopData| { - let tty = data.backend.tty().unwrap(); - let niri = &mut data.niri; - - match event { - UdevEvent::Added { device_id, path } => { - if !tty.session.is_active() { - debug!("skipping UdevEvent::Added as session is inactive"); - return; - } - - if let Err(err) = tty.device_added(device_id, &path, niri) { - warn!("error adding device: {err:?}"); - } - } - UdevEvent::Changed { device_id } => { - if !tty.session.is_active() { - debug!("skipping UdevEvent::Changed as session is inactive"); - return; - } - - tty.device_changed(device_id, niri) - } - UdevEvent::Removed { device_id } => { - if !tty.session.is_active() { - debug!("skipping UdevEvent::Removed as session is inactive"); - return; - } - - tty.device_removed(device_id, niri) - } - } - }); - event_loop - .register_dispatcher(udev_dispatcher.clone()) - .unwrap(); - - let mut libinput = Libinput::new_with_udev(LibinputSessionInterface::from(session.clone())); - libinput.udev_assign_seat(&seat_name).unwrap(); - - let input_backend = LibinputInputBackend::new(libinput.clone()); - event_loop - .insert_source(input_backend, |mut event, _, data| { - let tty = data.backend.tty().unwrap(); - let niri = &mut data.niri; - - niri.process_libinput_event(&mut event); - match niri.process_input_event(CompositorMod::Super, event) { - BackendAction::None => (), - BackendAction::ChangeVt(vt) => tty.change_vt(vt), - BackendAction::Suspend => { - if let Err(err) = suspend() { - warn!("error suspending: {err:?}"); - } - } - BackendAction::Screenshot => { - let active = niri.monitor_set.active_output().cloned(); - if let Some(active) = active { - if let Err(err) = niri.screenshot(tty.renderer(), &active) { - warn!("error taking screenshot: {err:?}"); - } - } - } - BackendAction::ToggleDebugTint => { - if let Some(device) = tty.output_device.as_mut() { - for (_, compositor) in &mut device.surfaces { - compositor - .set_debug_flags(compositor.debug_flags() ^ DebugFlags::TINT); - } - } - } - }; - }) - .unwrap(); - - let udev_dispatcher_c = udev_dispatcher.clone(); - event_loop - .insert_source(notifier, move |event, _, data| { - let tty = data.backend.tty().unwrap(); - let niri = &mut data.niri; - - match event { - SessionEvent::PauseSession => { - debug!("pausing session"); - - libinput.suspend(); - - if let Some(output_device) = &tty.output_device { - output_device.drm.pause(); - } - } - SessionEvent::ActivateSession => { - debug!("resuming session"); - - if libinput.resume().is_err() { - error!("error resuming libinput"); - } - - if let Some(output_device) = &mut tty.output_device { - // We had an output device, check if it's been removed. - let output_device_id = output_device.id; - if !udev_dispatcher_c - .as_source_ref() - .device_list() - .any(|(device_id, _)| device_id == output_device_id) - { - // The output device, if we had any, has been removed. - tty.device_removed(output_device_id, niri); - } else { - // It hasn't been removed, update its state as usual. - output_device.drm.activate(); - - // HACK: force reset the connectors to make resuming work across - // sleep. - let output_device = tty.output_device.as_mut().unwrap(); - let crtcs: Vec<_> = output_device - .drm_scanner - .crtcs() - .map(|(conn, crtc)| (conn.clone(), crtc)) - .collect(); - for (conn, crtc) in crtcs { - tty.connector_disconnected(niri, conn, crtc); - } - - let output_device = tty.output_device.as_mut().unwrap(); - let _ = output_device - .drm_scanner - .scan_connectors(&output_device.drm); - let crtcs: Vec<_> = output_device - .drm_scanner - .crtcs() - .map(|(conn, crtc)| (conn.clone(), crtc)) - .collect(); - for (conn, crtc) in crtcs { - if let Err(err) = tty.connector_connected(niri, conn, crtc) { - warn!("error connecting connector: {err:?}"); - } - } - - // // Refresh the connectors. - // tty.device_changed(output_device_id, niri); - - // // Refresh the state on unchanged connectors. - // let output_device = tty.output_device.as_mut().unwrap(); - // for drm_compositor in output_device.surfaces.values_mut() { - // if let Err(err) = drm_compositor.surface().reset_state() { - // warn!("error resetting DRM surface state: {err}"); - // } - // drm_compositor.reset_buffers(); - // } - - // niri.queue_redraw_all(); - } - } else { - // We didn't have an output device, check if it's been added. - for (device_id, path) in udev_dispatcher_c.as_source_ref().device_list() - { - if let Err(err) = tty.device_added(device_id, path, niri) { - warn!("error adding device: {err:?}"); - } - } - } - } - } - }) - .unwrap(); - - let primary_gpu_path = udev::primary_gpu(&seat_name).unwrap().unwrap(); - - Self { - session, - udev_dispatcher, - primary_gpu_path, - output_device: None, - } - } - - pub fn init(&mut self, niri: &mut Niri) { - for (device_id, path) in self.udev_dispatcher.clone().as_source_ref().device_list() { - if let Err(err) = self.device_added(device_id, path, niri) { - warn!("error adding device: {err:?}"); - } - } - } - - fn device_added( - &mut self, - device_id: dev_t, - path: &Path, - niri: &mut Niri, - ) -> anyhow::Result<()> { - if path != self.primary_gpu_path { - debug!("skipping non-primary device {path:?}"); - return Ok(()); - } - - debug!("adding device {path:?}"); - assert!(self.output_device.is_none()); - - let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK; - let fd = self.session.open(path, open_flags)?; - let device_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(fd)) }; - - let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?; - let gbm = GbmDevice::new(device_fd)?; - - let display = EGLDisplay::new(gbm.clone())?; - let egl_context = EGLContext::new(&display)?; - - let mut gles = unsafe { GlesRenderer::new(egl_context)? }; - gles.bind_wl_display(&niri.display_handle)?; - - let token = niri - .event_loop - .insert_source(drm_notifier, move |event, metadata, data| { - let tty = data.backend.tty().unwrap(); - match event { - DrmEvent::VBlank(crtc) => { - tracy_client::Client::running() - .unwrap() - .message("vblank", 0); - trace!("vblank {metadata:?}"); - - let device = tty.output_device.as_mut().unwrap(); - let drm_compositor = device.surfaces.get_mut(&crtc).unwrap(); - - let presentation_time = match metadata.as_mut().unwrap().time { - DrmEventTime::Monotonic(time) => time, - DrmEventTime::Realtime(_) => { - // Not supported. - - // This value will be ignored in the frame clock code. - Duration::ZERO - } - }; - - // Mark the last frame as submitted. - match drm_compositor.frame_submitted() { - Ok(Some(mut feedback)) => { - let refresh = - feedback.output().unwrap().current_mode().unwrap().refresh - as u32; - // FIXME: ideally should be monotonically increasing for a surface. - let seq = metadata.as_ref().unwrap().sequence as u64; - let flags = wp_presentation_feedback::Kind::Vsync - | wp_presentation_feedback::Kind::HwClock - | wp_presentation_feedback::Kind::HwCompletion; - - feedback.presented::<_, smithay::utils::Monotonic>( - presentation_time, - refresh, - seq, - flags, - ); - } - Ok(None) => (), - Err(err) => { - error!("error marking frame as submitted: {err}"); - } - } - - let output = data - .niri - .global_space - .outputs() - .find(|output| { - let tty_state: &TtyOutputState = output.user_data().get().unwrap(); - tty_state.device_id == device.id && tty_state.crtc == crtc - }) - .unwrap() - .clone(); - let output_state = data.niri.output_state.get_mut(&output).unwrap(); - output_state.waiting_for_vblank = false; - output_state.frame_clock.presented(presentation_time); - data.niri.queue_redraw(output); - } - DrmEvent::Error(error) => error!("DRM error: {error}"), - }; - }) - .unwrap(); - - let formats = Bind::::supported_formats(&gles).unwrap_or_default(); - - self.output_device = Some(OutputDevice { - id: device_id, - token, - drm, - gbm, - gles, - formats, - drm_scanner: DrmScanner::new(), - surfaces: HashMap::new(), - }); - - self.device_changed(device_id, niri); - - Ok(()) - } - - fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) { - let Some(device) = &mut self.output_device else { - return; - }; - if device.id != device_id { - return; - } - debug!("output device changed"); - - for event in device.drm_scanner.scan_connectors(&device.drm) { - match event { - DrmScanEvent::Connected { - connector, - crtc: Some(crtc), - } => { - if let Err(err) = self.connector_connected(niri, connector, crtc) { - warn!("error connecting connector: {err:?}"); - } - } - DrmScanEvent::Disconnected { - connector, - crtc: Some(crtc), - } => self.connector_disconnected(niri, connector, crtc), - _ => (), - } - } - } - - fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) { - let Some(device) = &mut self.output_device else { - return; - }; - if device_id != device.id { - return; - } - - let crtcs: Vec<_> = device - .drm_scanner - .crtcs() - .map(|(info, crtc)| (info.clone(), crtc)) - .collect(); - - for (connector, crtc) in crtcs { - self.connector_disconnected(niri, connector, crtc); - } - - let mut device = self.output_device.take().unwrap(); - device.gles.unbind_wl_display(); - niri.event_loop.remove(device.token); - } - - fn connector_connected( - &mut self, - niri: &mut Niri, - connector: connector::Info, - crtc: crtc::Handle, - ) -> anyhow::Result<()> { - let output_name = format!( - "{}-{}", - connector.interface().as_str(), - connector.interface_id(), - ); - debug!("connecting connector: {output_name}"); - - let device = self.output_device.as_mut().unwrap(); - - let mut mode = connector.modes().get(0); - connector.modes().iter().for_each(|m| { - debug!("mode: {m:?}"); - - if m.mode_type().contains(ModeTypeFlags::PREFERRED) { - // Pick the highest refresh rate. - if mode - .map(|curr| curr.vrefresh() < m.vrefresh()) - .unwrap_or(true) - { - mode = Some(m); - } - } - }); - let mode = mode.ok_or_else(|| anyhow!("no mode"))?; - debug!("picking mode: {mode:?}"); - - let surface = device - .drm - .create_surface(crtc, *mode, &[connector.handle()])?; - - // Create GBM allocator. - let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT; - let allocator = GbmAllocator::new(device.gbm.clone(), gbm_flags); - - // Update the output mode. - let (physical_width, physical_height) = connector.size().unwrap_or((0, 0)); - - let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle()) - .map(|info| (info.manufacturer, info.model)) - .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); - - let output = Output::new( - output_name, - PhysicalProperties { - size: (physical_width as i32, physical_height as i32).into(), - subpixel: Subpixel::Unknown, - model, - make, - }, - ); - let wl_mode = Mode::from(*mode); - output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into())); - output.set_preferred(wl_mode); - - output.user_data().insert_if_missing(|| TtyOutputState { - device_id: device.id, - crtc, - }); - - // Create the compositor. - let compositor = DrmCompositor::new( - OutputModeSource::Auto(output.clone()), - surface, - None, - allocator, - device.gbm.clone(), - SUPPORTED_COLOR_FORMATS, - device.formats.clone(), - device.drm.cursor_size(), - Some(device.gbm.clone()), - )?; - - let res = device.surfaces.insert(crtc, compositor); - assert!(res.is_none(), "crtc must not have already existed"); - - niri.add_output(output.clone(), Some(refresh_interval(*mode))); - niri.queue_redraw(output); - - Ok(()) - } - - fn connector_disconnected( - &mut self, - niri: &mut Niri, - connector: connector::Info, - crtc: crtc::Handle, - ) { - debug!("disconnecting connector: {connector:?}"); - let device = self.output_device.as_mut().unwrap(); - - if device.surfaces.remove(&crtc).is_none() { - debug!("crts wasn't enabled"); - return; - } - - let output = niri - .global_space - .outputs() - .find(|output| { - let tty_state: &TtyOutputState = output.user_data().get().unwrap(); - tty_state.device_id == device.id && tty_state.crtc == crtc - }) - .unwrap() - .clone(); - - niri.remove_output(&output); - } - - pub fn seat_name(&self) -> String { - self.session.seat() - } - - pub fn renderer(&mut self) -> &mut GlesRenderer { - &mut self.output_device.as_mut().unwrap().gles - } - - pub fn render( - &mut self, - niri: &mut Niri, - output: &Output, - elements: &[OutputRenderElements], - ) { - let _span = tracy_client::span!("Tty::render"); - - let device = self.output_device.as_mut().unwrap(); - let tty_state: &TtyOutputState = output.user_data().get().unwrap(); - let drm_compositor = device.surfaces.get_mut(&tty_state.crtc).unwrap(); - - match drm_compositor.render_frame::<_, _, GlesTexture>( - &mut device.gles, - elements, - BACKGROUND_COLOR, - ) { - Ok(res) => { - assert!(!res.needs_sync()); - if res.damage.is_some() { - let presentation_feedbacks = - niri.take_presentation_feedbacks(output, &res.states); - - match drm_compositor.queue_frame(presentation_feedbacks) { - Ok(()) => { - niri.output_state - .get_mut(output) - .unwrap() - .waiting_for_vblank = true - } - Err(err) => { - error!("error queueing frame: {err}"); - } - } - } - } - Err(err) => { - // Can fail if we switched to a different TTY. - error!("error rendering frame: {err}"); - } - } - } - - fn change_vt(&mut self, vt: i32) { - if let Err(err) = self.session.change_vt(vt) { - error!("error changing VT: {err}"); - } - } -} - -fn refresh_interval(mode: DrmMode) -> Duration { - let clock = mode.clock() as u64; - let htotal = mode.hsync().2 as u64; - let vtotal = mode.vsync().2 as u64; - - let mut numerator = htotal * vtotal * 1_000_000; - let mut denominator = clock; - - if mode.flags().contains(ModeFlags::INTERLACE) { - denominator *= 2; - } - - if mode.flags().contains(ModeFlags::DBLSCAN) { - numerator *= 2; - } - - if mode.vscan() > 1 { - numerator *= mode.vscan() as u64; - } - - let refresh_interval = (numerator + denominator / 2) / denominator; - Duration::from_nanos(refresh_interval) -} - -fn suspend() -> anyhow::Result<()> { - let conn = zbus::blocking::Connection::system().context("error connecting to system bus")?; - let manager = logind_zbus::manager::ManagerProxyBlocking::new(&conn) - .context("error creating login manager proxy")?; - manager.suspend(true).context("error suspending") -} diff --git a/src/winit.rs b/src/winit.rs deleted file mode 100644 index 35bf3407..00000000 --- a/src/winit.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::time::Duration; - -use smithay::backend::renderer::damage::OutputDamageTracker; -use smithay::backend::renderer::gles::GlesRenderer; -use smithay::backend::renderer::{DebugFlags, Renderer}; -use smithay::backend::winit::{self, WinitError, WinitEvent, WinitEventLoop, WinitGraphicsBackend}; -use smithay::output::{Mode, Output, PhysicalProperties, Subpixel}; -use smithay::reexports::calloop::timer::{TimeoutAction, Timer}; -use smithay::reexports::calloop::LoopHandle; -use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; -use smithay::reexports::winit::dpi::LogicalSize; -use smithay::reexports::winit::window::WindowBuilder; -use smithay::utils::Transform; - -use crate::input::{BackendAction, CompositorMod}; -use crate::niri::OutputRenderElements; -use crate::utils::get_monotonic_time; -use crate::{LoopData, Niri}; - -pub struct Winit { - output: Output, - backend: WinitGraphicsBackend, - winit_event_loop: WinitEventLoop, - damage_tracker: OutputDamageTracker, -} - -impl Winit { - pub fn new(event_loop: LoopHandle) -> Self { - let builder = WindowBuilder::new() - .with_inner_size(LogicalSize::new(1280.0, 800.0)) - // .with_resizable(false) - .with_title("niri"); - let (backend, winit_event_loop) = winit::init_from_builder(builder).unwrap(); - - let mode = Mode { - size: backend.window_size().physical_size, - refresh: 60_000, - }; - - let output = Output::new( - "winit".to_string(), - PhysicalProperties { - size: (0, 0).into(), - subpixel: Subpixel::Unknown, - make: "Smithay".into(), - model: "Winit".into(), - }, - ); - output.change_current_state( - Some(mode), - Some(Transform::Flipped180), - None, - Some((0, 0).into()), - ); - output.set_preferred(mode); - - let damage_tracker = OutputDamageTracker::from_output(&output); - - let timer = Timer::immediate(); - event_loop - .insert_source(timer, move |_, _, data| { - let winit = data.backend.winit().unwrap(); - winit.dispatch(&mut data.niri); - TimeoutAction::ToDuration(Duration::from_micros(16667)) - }) - .unwrap(); - - Self { - output, - backend, - winit_event_loop, - damage_tracker, - } - } - - pub fn init(&mut self, niri: &mut Niri) { - // For some reason, binding the display here causes damage tracker artifacts. - // - // use smithay::backend::renderer::ImportEgl; - // - // if let Err(err) = self - // .backend - // .renderer() - // .bind_wl_display(&niri.display_handle) - // { - // warn!("error binding renderer wl_display: {err}"); - // } - niri.add_output(self.output.clone(), None); - } - - fn dispatch(&mut self, niri: &mut Niri) { - let renderer = self.backend.renderer(); - let res = self - .winit_event_loop - .dispatch_new_events(|event| match event { - WinitEvent::Resized { size, .. } => { - self.output.change_current_state( - Some(Mode { - size, - refresh: 60_000, - }), - None, - None, - None, - ); - niri.output_resized(self.output.clone())