mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
6a2c6261df
* Implement custom modes and modelines Co-authored-by: ToxicMushroom <32853531+ToxicMushroom@users.noreply.github.com> * fixes * refactor mode and modeline kdl parsers. * add IPC parse checks * refactor: address feedback * fix: add missing > 0 refresh rate check * move things around * fixes * wiki fixes --------- Co-authored-by: Christian Meissl <meissl.christian@gmail.com> Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
169 lines
5.4 KiB
Rust
169 lines
5.4 KiB
Rust
//! Headless backend for tests.
|
|
//!
|
|
//! This can eventually grow into a more complete backend if needed, but for now it's missing some
|
|
//! crucial parts like dmabufs.
|
|
|
|
use std::mem;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use anyhow::Context as _;
|
|
use niri_config::OutputName;
|
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
|
use smithay::backend::egl::native::EGLSurfacelessDisplay;
|
|
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
|
use smithay::backend::renderer::element::RenderElementStates;
|
|
use smithay::backend::renderer::gles::GlesRenderer;
|
|
use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
|
|
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
|
use smithay::utils::Size;
|
|
use smithay::wayland::presentation::Refresh;
|
|
|
|
use super::{IpcOutputMap, OutputId, RenderResult};
|
|
use crate::niri::{Niri, RedrawState};
|
|
use crate::render_helpers::{resources, shaders};
|
|
use crate::utils::{get_monotonic_time, logical_output};
|
|
|
|
pub struct Headless {
|
|
renderer: Option<GlesRenderer>,
|
|
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
|
|
}
|
|
|
|
impl Headless {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
renderer: None,
|
|
ipc_outputs: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn init(&mut self, _niri: &mut Niri) {}
|
|
|
|
pub fn add_renderer(&mut self) -> anyhow::Result<()> {
|
|
if self.renderer.is_some() {
|
|
error!("add_renderer: renderer must not already exist");
|
|
return Ok(());
|
|
}
|
|
|
|
let mut renderer = unsafe {
|
|
let display =
|
|
EGLDisplay::new(EGLSurfacelessDisplay).context("error creating EGL display")?;
|
|
let context = EGLContext::new(&display).context("error creating EGL context")?;
|
|
GlesRenderer::new(context).context("error creating renderer")?
|
|
};
|
|
|
|
resources::init(&mut renderer);
|
|
shaders::init(&mut renderer);
|
|
|
|
self.renderer = Some(renderer);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_output(&mut self, niri: &mut Niri, n: u8, size: (u16, u16)) {
|
|
let connector = format!("headless-{n}");
|
|
let make = "niri".to_string();
|
|
let model = "headless".to_string();
|
|
let serial = n.to_string();
|
|
|
|
let output = Output::new(
|
|
connector.clone(),
|
|
PhysicalProperties {
|
|
size: (0, 0).into(),
|
|
subpixel: Subpixel::Unknown,
|
|
make: make.clone(),
|
|
model: model.clone(),
|
|
serial_number: serial.clone(),
|
|
},
|
|
);
|
|
|
|
let mode = Mode {
|
|
size: Size::from((i32::from(size.0), i32::from(size.1))),
|
|
refresh: 60_000,
|
|
};
|
|
output.change_current_state(Some(mode), None, None, None);
|
|
output.set_preferred(mode);
|
|
|
|
output.user_data().insert_if_missing(|| OutputName {
|
|
connector,
|
|
make: Some(make),
|
|
model: Some(model),
|
|
serial: Some(serial),
|
|
});
|
|
|
|
let physical_properties = output.physical_properties();
|
|
self.ipc_outputs.lock().unwrap().insert(
|
|
OutputId::next(),
|
|
niri_ipc::Output {
|
|
name: output.name(),
|
|
make: physical_properties.make,
|
|
model: physical_properties.model,
|
|
serial: None,
|
|
physical_size: None,
|
|
modes: vec![niri_ipc::Mode {
|
|
width: size.0,
|
|
height: size.1,
|
|
refresh_rate: 60_000,
|
|
is_preferred: true,
|
|
}],
|
|
current_mode: Some(0),
|
|
is_custom_mode: true,
|
|
vrr_supported: false,
|
|
vrr_enabled: false,
|
|
logical: Some(logical_output(&output)),
|
|
},
|
|
);
|
|
|
|
niri.add_output(output, None, false);
|
|
}
|
|
|
|
pub fn seat_name(&self) -> String {
|
|
"headless".to_owned()
|
|
}
|
|
|
|
pub fn with_primary_renderer<T>(
|
|
&mut self,
|
|
f: impl FnOnce(&mut GlesRenderer) -> T,
|
|
) -> Option<T> {
|
|
self.renderer.as_mut().map(f)
|
|
}
|
|
|
|
pub fn render(&mut self, niri: &mut Niri, output: &Output) -> RenderResult {
|
|
let states = RenderElementStates::default();
|
|
let mut presentation_feedbacks = niri.take_presentation_feedbacks(output, &states);
|
|
presentation_feedbacks.presented::<_, smithay::utils::Monotonic>(
|
|
get_monotonic_time(),
|
|
Refresh::Unknown,
|
|
0,
|
|
wp_presentation_feedback::Kind::empty(),
|
|
);
|
|
|
|
let output_state = niri.output_state.get_mut(output).unwrap();
|
|
match mem::replace(&mut output_state.redraw_state, RedrawState::Idle) {
|
|
RedrawState::Idle => unreachable!(),
|
|
RedrawState::Queued => (),
|
|
RedrawState::WaitingForVBlank { .. } => unreachable!(),
|
|
RedrawState::WaitingForEstimatedVBlank(_) => unreachable!(),
|
|
RedrawState::WaitingForEstimatedVBlankAndQueued(_) => unreachable!(),
|
|
}
|
|
|
|
output_state.frame_callback_sequence = output_state.frame_callback_sequence.wrapping_add(1);
|
|
|
|
// FIXME: request redraw on unfinished animations remain
|
|
|
|
RenderResult::Submitted
|
|
}
|
|
|
|
pub fn import_dmabuf(&mut self, _dmabuf: &Dmabuf) -> bool {
|
|
unimplemented!()
|
|
}
|
|
|
|
pub fn ipc_outputs(&self) -> Arc<Mutex<IpcOutputMap>> {
|
|
self.ipc_outputs.clone()
|
|
}
|
|
}
|
|
|
|
impl Default for Headless {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|