mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Refactor everything, add initial tiling code
This commit is contained in:
+165
-148
@@ -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 Some(device) = &mut self.output_device else {
|
||||
return;
|
||||
};
|
||||
if device.id != device_id {
|
||||
return;
|
||||
}
|
||||
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:?}");
|
||||
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;
|
||||
}
|
||||
|
||||
// FIXME: remove wl_output.
|
||||
niri.event_loop.remove(output_device.token);
|
||||
niri.output = None;
|
||||
output_device.gles.unbind_wl_display();
|
||||
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();
|
||||
|
||||
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:?}");
|
||||
debug!("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
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user