diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-08-13 12:46:53 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-08-13 12:46:53 +0400 |
| commit | 95c810c855a27a28f4dfa7dc6b949fef0901c7b2 (patch) | |
| tree | c240dd8d8c6eac7cd18c507fbe35724ca7de8aeb /src/tty.rs | |
| parent | e02e35f9c61e103a01640d3dc95a894e8855e1c9 (diff) | |
| download | niri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.tar.gz niri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.tar.bz2 niri-95c810c855a27a28f4dfa7dc6b949fef0901c7b2.zip | |
Refactor everything, add initial tiling code
Diffstat (limited to 'src/tty.rs')
| -rw-r--r-- | src/tty.rs | 313 |
1 files changed, 165 insertions, 148 deletions
@@ -1,10 +1,11 @@ +use std::collections::{HashMap, HashSet}; use std::os::fd::FromRawFd; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use anyhow::anyhow; use smithay::backend::allocator::dmabuf::Dmabuf; use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; -use smithay::backend::allocator::Fourcc; +use smithay::backend::allocator::{Format as DrmFormat, Fourcc}; use smithay::backend::drm::compositor::DrmCompositor; use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent}; use smithay::backend::egl::{EGLContext, EGLDisplay}; @@ -17,14 +18,12 @@ use smithay::backend::session::{Event as SessionEvent, Session}; use smithay::backend::udev::{self, UdevBackend, UdevEvent}; use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel}; use smithay::reexports::calloop::{LoopHandle, RegistrationToken}; -use smithay::reexports::drm::control::connector::{ - Interface as ConnectorInterface, State as ConnectorState, -}; -use smithay::reexports::drm::control::{Device, ModeTypeFlags}; +use smithay::reexports::drm::control::{connector, crtc, ModeTypeFlags}; use smithay::reexports::input::Libinput; use smithay::reexports::nix::fcntl::OFlag; use smithay::reexports::nix::libc::dev_t; use smithay::utils::DeviceFd; +use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner}; use smithay_drm_extras::edid::EdidInfo; use crate::backend::Backend; @@ -46,11 +45,19 @@ type GbmDrmCompositor = struct OutputDevice { id: dev_t, - path: PathBuf, token: RegistrationToken, drm: DrmDevice, + gbm: GbmDevice<DrmDeviceFd>, gles: GlesRenderer, - drm_compositor: GbmDrmCompositor, + formats: HashSet<DrmFormat>, + drm_scanner: DrmScanner, + surfaces: HashMap<crtc::Handle, GbmDrmCompositor>, +} + +#[derive(Debug, Clone, Copy)] +struct TtyOutputState { + device_id: dev_t, + crtc: crtc::Handle, } impl Backend for Tty { @@ -65,6 +72,7 @@ impl Backend for Tty { fn render( &mut self, niri: &mut Niri, + output: &Output, elements: &[OutputRenderElements< GlesRenderer, WaylandSurfaceRenderElement<GlesRenderer>, @@ -72,19 +80,26 @@ impl Backend for Tty { ) { let _span = tracy_client::span!("Tty::render"); - let output_device = self.output_device.as_mut().unwrap(); - let drm_compositor = &mut output_device.drm_compositor; + 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 output_device.gles, + &mut device.gles, elements, BACKGROUND_COLOR, ) { Ok(res) => { assert!(!res.needs_sync()); + // debug!("{:?}", res); if res.damage.is_some() { - match output_device.drm_compositor.queue_frame(()) { - Ok(()) => niri.waiting_for_vblank = true, + match drm_compositor.queue_frame(()) { + Ok(()) => { + niri.output_state + .get_mut(output) + .unwrap() + .waiting_for_vblank = true + } Err(err) => { error!("error queueing frame: {err}"); } @@ -142,13 +157,15 @@ impl Tty { if let Some(output_device) = &mut tty.output_device { output_device.drm.activate(); - if let Err(err) = output_device.drm_compositor.surface().reset_state() { - warn!("error resetting DRM surface state: {err}"); + 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(); } - output_device.drm_compositor.reset_buffers(); } - niri.queue_redraw(); + niri.queue_redraw_all(); } } }) @@ -166,7 +183,7 @@ impl Tty { pub fn init(&mut self, niri: &mut Niri) { let backend = UdevBackend::new(&self.session.seat()).unwrap(); for (device_id, path) in backend.device_list() { - if let Err(err) = self.device_added(device_id, path.to_owned(), niri) { + if let Err(err) = self.device_added(device_id, path, niri) { warn!("error adding device: {err:?}"); } } @@ -178,24 +195,21 @@ impl Tty { match event { UdevEvent::Added { device_id, path } => { - if let Err(err) = tty.device_added(device_id, path, niri) { + if let Err(err) = tty.device_added(device_id, &path, niri) { warn!("error adding device: {err:?}"); } - niri.queue_redraw(); } UdevEvent::Changed { device_id } => tty.device_changed(device_id, niri), UdevEvent::Removed { device_id } => tty.device_removed(device_id, niri), } }) .unwrap(); - - niri.queue_redraw(); } fn device_added( &mut self, device_id: dev_t, - path: PathBuf, + path: &Path, niri: &mut Niri, ) -> anyhow::Result<()> { if path != self.primary_gpu_path { @@ -207,7 +221,7 @@ impl Tty { 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 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)?; @@ -219,23 +233,22 @@ impl Tty { let mut gles = unsafe { GlesRenderer::new(egl_context)? }; gles.bind_wl_display(&niri.display_handle)?; - let drm_compositor = self.create_drm_compositor(&drm, &gbm, &gles, niri)?; - let token = niri .event_loop .insert_source(drm_notifier, move |event, metadata, data| { let tty = data.tty.as_mut().unwrap(); match event { - DrmEvent::VBlank(_crtc) => { + DrmEvent::VBlank(crtc) => { tracy_client::Client::running() .unwrap() .message("vblank", 0); trace!("vblank {metadata:?}"); - let output_device = tty.output_device.as_mut().unwrap(); + let device = tty.output_device.as_mut().unwrap(); + let drm_compositor = device.surfaces.get_mut(&crtc).unwrap(); // Mark the last frame as submitted. - if let Err(err) = output_device.drm_compositor.frame_submitted() { + if let Err(err) = drm_compositor.frame_submitted() { error!("error marking frame as submitted: {err}"); } @@ -244,103 +257,111 @@ impl Tty { // .windows // .mark_presented(&output_device.last_render_states, metadata); - data.niri.waiting_for_vblank = false; - data.niri.queue_redraw(); + 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(); + data.niri + .output_state + .get_mut(&output) + .unwrap() + .waiting_for_vblank = false; + data.niri.queue_redraw(output); } DrmEvent::Error(error) => error!("DRM error: {error}"), }; }) .unwrap(); + let formats = Bind::<Dmabuf>::supported_formats(&gles).unwrap_or_default(); + self.output_device = Some(OutputDevice { id: device_id, - path, token, drm, + gbm, gles, - drm_compositor, + 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) { - if let Some(output_device) = &self.output_device { - if output_device.id == device_id { - debug!("output device changed"); - - let path = output_device.path.clone(); - self.device_removed(device_id, niri); - if let Err(err) = self.device_added(device_id, path, niri) { - warn!("error adding device: {err:?}"); + 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) { - if let Some(mut output_device) = self.output_device.take() { - if output_device.id != device_id { - self.output_device = Some(output_device); - return; - } + let Some(device) = self.output_device.take() else { + return; + }; + if device.id != device_id { + // It wasn't the output device, put it back in. + self.output_device = Some(device); + return; + } + + let crtcs: Vec<_> = device + .drm_scanner + .crtcs() + .map(|(info, crtc)| (info.clone(), crtc)) + .collect(); - // FIXME: remove wl_output. - niri.event_loop.remove(output_device.token); - niri.output = None; - output_device.gles.unbind_wl_display(); + for (connector, crtc) in crtcs { + self.connector_disconnected(niri, connector, crtc); } + + niri.event_loop.remove(device.token); } - fn create_drm_compositor( + fn connector_connected( &mut self, - drm: &DrmDevice, - gbm: &GbmDevice<DrmDeviceFd>, - gles: &GlesRenderer, niri: &mut Niri, - ) -> anyhow::Result<GbmDrmCompositor> { - let formats = Bind::<Dmabuf>::supported_formats(gles) - .ok_or_else(|| anyhow!("no supported formats"))?; - let resources = drm.resource_handles()?; - - let mut connector = None; - let mut edp_connector = None; - resources - .connectors() - .iter() - .filter_map(|conn| match drm.get_connector(*conn, true) { - Ok(info) => Some(info), - Err(err) => { - warn!("error probing connector: {err}"); - None - } - }) - .inspect(|conn| { - debug!( - "connector: {}-{}, {:?}, {} modes", - conn.interface().as_str(), - conn.interface_id(), - conn.state(), - conn.modes().len(), - ); - }) - .filter(|conn| conn.state() == ConnectorState::Connected) - .for_each(|conn| { - connector = Some(conn.clone()); - - if conn.interface() == ConnectorInterface::EmbeddedDisplayPort { - edp_connector = Some(conn); - } - }); - // Since we're only using one output at the moment, prefer eDP. - let connector = edp_connector - .or(connector) - .ok_or_else(|| anyhow!("no compatible connector"))?; - info!( - "picking connector: {}-{}", + 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| { @@ -357,57 +378,20 @@ impl Tty { } }); let mode = mode.ok_or_else(|| anyhow!("no mode"))?; - info!("picking mode: {mode:?}"); - - let surface = connector - .encoders() - .iter() - .filter_map(|enc| match drm.get_encoder(*enc) { - Ok(info) => Some(info), - Err(err) => { - warn!("error probing encoder: {err}"); - None - } - }) - .flat_map(|enc| { - // Get all CRTCs compatible with the encoder. - let mut crtcs = resources.filter_crtcs(enc.possible_crtcs()); - - // Sort by maximum number of overlay planes. - crtcs.sort_by_cached_key(|crtc| match drm.planes(crtc) { - Ok(planes) => -(planes.overlay.len() as isize), - Err(err) => { - warn!("error probing planes for CRTC: {err}"); - 0 - } - }); + debug!("picking mode: {mode:?}"); - crtcs - }) - .find_map( - |crtc| match drm.create_surface(crtc, *mode, &[connector.handle()]) { - Ok(surface) => Some(surface), - Err(err) => { - warn!("error creating DRM surface: {err}"); - None - } - }, - ); - let surface = surface.ok_or_else(|| anyhow!("no surface"))?; + let surface = device + .drm + .create_surface(crtc, *mode, &[connector.handle()])?; // Create GBM allocator. let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT; - let allocator = GbmAllocator::new(gbm.clone(), gbm_flags); + 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 output_name = format!( - "{}-{}", - connector.interface().as_str(), - connector.interface_id(), - ); - let (make, model) = EdidInfo::for_connector(drm, connector.handle()) + let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle()) .map(|info| (info.manufacturer, info.model)) .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); @@ -424,25 +408,58 @@ impl Tty { output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into())); output.set_preferred(wl_mode); - // FIXME: store this somewhere to remove on disconnect, etc. - let _global = output.create_global::<Niri>(&niri.display_handle); - niri.space.map_output(&output, (0, 0)); - niri.output = Some(output.clone()); - // windows.set_output(); + output.user_data().insert_if_missing(|| TtyOutputState { + device_id: device.id, + crtc, + }); // Create the compositor. let compositor = DrmCompositor::new( - OutputModeSource::Auto(output), + OutputModeSource::Auto(output.clone()), surface, None, allocator, - gbm.clone(), + device.gbm.clone(), SUPPORTED_COLOR_FORMATS, - formats, - drm.cursor_size(), - Some(gbm.clone()), + device.formats.clone(), + device.drm.cursor_size(), + Some(device.gbm.clone()), )?; - Ok(compositor) + + let res = device.surfaces.insert(crtc, compositor); + assert!(res.is_none(), "crtc must not have already existed"); + + niri.add_output(output.clone()); + 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); } fn change_vt(&mut self, vt: i32) { |
