feat: manage multiple DRM output pipelines

This commit is contained in:
Samuel Onoja committed 2026-09-25 01:47:55 +01:00
1 parent 42ad4e1f68
commit 1934cef705
3 files changed
+331 -101

No files matched your search

+278 -96
View File
@@ -1,4 +1,10 @@
use std::{collections::HashMap, error::Error, io, path::Path, time::Duration}; use std::{
collections::{HashMap, HashSet},
error::Error,
io,
path::Path,
time::Duration,
};
use smithay::{ use smithay::{
backend::{ backend::{
@@ -24,6 +30,7 @@ use smithay::{
input::Libinput, input::Libinput,
rustix::fs::OFlags, rustix::fs::OFlags,
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind, wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
wayland_server::backend::GlobalId,
}, },
utils::{DeviceFd, Monotonic, Time, Transform}, utils::{DeviceFd, Monotonic, Time, Transform},
wayland::presentation::Refresh, wayland::presentation::Refresh,
@@ -35,7 +42,7 @@ pub struct DirectBackendState {
pub session: LibSeatSession, pub session: LibSeatSession,
pub active: bool, pub active: bool,
devices: HashMap<DrmNode, DirectDevice>, devices: HashMap<DrmNode, DirectDevice>,
presentation: PresentationClock, presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>,
} }
#[derive(Default)] #[derive(Default)]
@@ -48,18 +55,16 @@ struct PresentationClock {
struct DirectDevice { struct DirectDevice {
drm: DrmDevice, drm: DrmDevice,
#[allow(dead_code)]
gbm: GbmDevice<DrmDeviceFd>, gbm: GbmDevice<DrmDeviceFd>,
#[allow(dead_code)]
renderer: GlesRenderer, renderer: GlesRenderer,
#[allow(dead_code)] outputs: HashMap<crtc::Handle, DirectOutput>,
}
struct DirectOutput {
connector: connector::Handle, connector: connector::Handle,
#[allow(dead_code)]
crtc: crtc::Handle,
#[allow(dead_code)]
mode: DrmMode, mode: DrmMode,
#[allow(dead_code)]
output: Output, output: Output,
global: GlobalId,
surface: GbmBufferedSurface<GbmAllocator<DrmDeviceFd>, OutputPresentationFeedback>, surface: GbmBufferedSurface<GbmAllocator<DrmDeviceFd>, OutputPresentationFeedback>,
damage_tracker: OutputDamageTracker, damage_tracker: OutputDamageTracker,
frame_pending: bool, frame_pending: bool,
@@ -92,7 +97,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
session, session,
active: session_active, active: session_active,
devices: HashMap::new(), devices: HashMap::new(),
presentation: PresentationClock::default(), presentation: HashMap::new(),
}); });
open_primary_device(event_loop, state, &primary_path)?; open_primary_device(event_loop, state, &primary_path)?;
@@ -107,12 +112,18 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
SessionEvent::PauseSession => { SessionEvent::PauseSession => {
if let Some(backend) = state.direct_backend.as_mut() { if let Some(backend) = state.direct_backend.as_mut() {
backend.active = false; backend.active = false;
backend.presentation.reset_timing(); backend
.presentation
.values_mut()
.for_each(PresentationClock::reset_timing);
backend.devices.values_mut().for_each(|device| { backend.devices.values_mut().for_each(|device| {
device.drm.pause(); device.drm.pause();
device.surface.reset_buffers(); device.outputs.values_mut().for_each(|output| {
device.damage_tracker = OutputDamageTracker::from_output(&device.output); output.surface.reset_buffers();
device.frame_pending = false; output.damage_tracker =
OutputDamageTracker::from_output(&output.output);
output.frame_pending = false;
});
}); });
} }
libinput_context.suspend(); libinput_context.suspend();
@@ -136,15 +147,23 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
})?; })?;
event_loop event_loop
.handle() .handle()
.insert_source(udev_backend, |event, _, _state| match event { .insert_source(udev_backend, |event, _, state| match event {
UdevEvent::Added { device_id, path } => { UdevEvent::Added { device_id, path } => {
tracing::info!(?device_id, ?path, "DRM device added"); if let Some(node) = direct_node_for_device(state, device_id) {
rescan_device(state, node);
} else {
tracing::info!(?device_id, ?path, "additional DRM device discovered");
}
} }
UdevEvent::Changed { device_id } => { UdevEvent::Changed { device_id } => {
tracing::debug!(?device_id, "DRM device changed"); if let Some(node) = direct_node_for_device(state, device_id) {
rescan_device(state, node);
}
} }
UdevEvent::Removed { device_id } => { UdevEvent::Removed { device_id } => {
tracing::info!(?device_id, "DRM device removed"); if let Some(node) = direct_node_for_device(state, device_id) {
remove_device(state, node);
}
} }
})?; })?;
@@ -182,25 +201,20 @@ fn open_primary_device(
state state
.dmabuf_state .dmabuf_state
.create_global::<Ferese>(&display_handle, dmabuf_formats); .create_global::<Ferese>(&display_handle, dmabuf_formats);
let (connector, crtc, mode) = select_output(&drm)?; let selections = select_outputs(&drm)?;
let output = create_output(state, &connector, mode); let mut outputs = HashMap::new();
if let Some(backend) = state.direct_backend.as_mut() { for (connector, crtc, mode) in selections {
backend let output = create_direct_output(state, &mut drm, &gbm, &renderer, connector, crtc, mode)?;
state
.direct_backend
.as_mut()
.expect("direct backend state remains initialized")
.presentation .presentation
.entry((node, crtc))
.or_default()
.set_refresh(OutputMode::from(mode).refresh); .set_refresh(OutputMode::from(mode).refresh);
outputs.insert(crtc, output);
} }
let drm_surface = drm.create_surface(crtc, mode, &[connector.handle()])?;
let allocator = GbmAllocator::new(
gbm.clone(),
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
);
let surface = GbmBufferedSurface::new(
drm_surface,
allocator,
&[Fourcc::Argb8888, Fourcc::Abgr8888],
renderer.dmabuf_formats(),
)?;
let damage_tracker = OutputDamageTracker::from_output(&output);
event_loop event_loop
.handle() .handle()
@@ -211,7 +225,8 @@ fn open_primary_device(
.direct_backend .direct_backend
.as_mut() .as_mut()
.and_then(|backend| backend.devices.get_mut(&node)) .and_then(|backend| backend.devices.get_mut(&node))
.and_then(|device| match device.surface.frame_submitted() { .and_then(|device| device.outputs.get_mut(&crtc))
.and_then(|output| match output.surface.frame_submitted() {
Ok(feedback) => feedback, Ok(feedback) => feedback,
Err(error) => { Err(error) => {
tracing::error!(?node, ?crtc, %error, "failed to retire DRM frame"); tracing::error!(?node, ?crtc, %error, "failed to retire DRM frame");
@@ -223,21 +238,30 @@ fn open_primary_device(
{ {
feedback.presented( feedback.presented(
Time::<Monotonic>::from(time), Time::<Monotonic>::from(time),
Refresh::fixed(refresh_duration(state, node)), Refresh::fixed(refresh_duration(state, node, crtc)),
u64::from(metadata.sequence), u64::from(metadata.sequence),
Kind::Vsync | Kind::HwClock | Kind::HwCompletion, Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
); );
} }
if let Some(device) = state let focused = state
.direct_backend
.as_ref()
.and_then(|backend| backend.devices.get(&node))
.and_then(|device| device.outputs.get(&crtc))
.is_some_and(|output| state.is_focused_output(&output.output));
if let Some(output) = state
.direct_backend .direct_backend
.as_mut() .as_mut()
.and_then(|backend| backend.devices.get_mut(&node)) .and_then(|backend| backend.devices.get_mut(&node))
.and_then(|device| device.outputs.get_mut(&crtc))
{ {
device.frame_pending = false; output.frame_pending = false;
}
if focused {
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
} }
state.record_drm_presentation(metadata.time, metadata.sequence);
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip"); tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
render_device(state, node); render_output(state, node, crtc);
} }
} }
DrmEvent::Error(error) => { DrmEvent::Error(error) => {
@@ -256,30 +280,30 @@ fn open_primary_device(
drm, drm,
gbm, gbm,
renderer, renderer,
connector: connector.handle(), outputs,
crtc,
mode,
output,
surface,
damage_tracker,
frame_pending: false,
}, },
); );
state.relayout(); state.relayout();
render_device(state, node); render_all(state);
tracing::info!(?node, ?path, ?crtc, connector = %connector, "initialized primary DRM/GBM device"); tracing::info!(?node, ?path, "initialized DRM/GBM device outputs");
Ok(()) Ok(())
} }
pub fn render_all(state: &mut Ferese) { pub fn render_all(state: &mut Ferese) {
let nodes = state let outputs = state
.direct_backend .direct_backend
.as_ref() .as_ref()
.map(|backend| backend.devices.keys().copied().collect::<Vec<_>>()) .map(|backend| {
backend
.devices
.iter()
.flat_map(|(node, device)| device.outputs.keys().map(|crtc| (*node, *crtc)))
.collect::<Vec<_>>()
})
.unwrap_or_default(); .unwrap_or_default();
for node in nodes { for (node, crtc) in outputs {
render_device(state, node); render_output(state, node, crtc);
} }
} }
@@ -293,7 +317,7 @@ pub fn switch_vt(state: &mut Ferese, vt: i32) {
} }
} }
fn render_device(state: &mut Ferese, node: DrmNode) { fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
let Some(mut device) = state let Some(mut device) = state
.direct_backend .direct_backend
.as_mut() .as_mut()
@@ -301,34 +325,39 @@ fn render_device(state: &mut Ferese, node: DrmNode) {
else { else {
return; return;
}; };
if !device.drm.is_active() || device.frame_pending { let Some(mut output) = device.outputs.remove(&crtc) else {
restore_device(state, node, device);
return;
};
if !device.drm.is_active() || output.frame_pending {
device.outputs.insert(crtc, output);
restore_device(state, node, device); restore_device(state, node, device);
return; return;
} }
let rendered = (|| -> Result<bool, Box<dyn Error>> { let rendered = (|| -> Result<bool, Box<dyn Error>> {
state.process_dmabuf_imports(&mut device.renderer); state.process_dmabuf_imports(&mut device.renderer);
let (mut buffer, age) = device.surface.next_buffer()?; let (mut buffer, age) = output.surface.next_buffer()?;
let elements = animated_window_elements(state, &mut device.renderer, &device.output); let elements = animated_window_elements(state, &mut device.renderer, &output.output);
let mut framebuffer = device.renderer.bind(&mut buffer)?; let mut framebuffer = device.renderer.bind(&mut buffer)?;
let result = device.damage_tracker.render_output( let result = output.damage_tracker.render_output(
&mut device.renderer, &mut device.renderer,
&mut framebuffer, &mut framebuffer,
usize::from(age), usize::from(age),
&elements, &elements,
[0.035, 0.04, 0.055, 1.0], [0.035, 0.04, 0.055, 1.0],
)?; )?;
if state.process_screencopies(&mut device.renderer, &framebuffer, &device.output) { if state.process_screencopies(&mut device.renderer, &framebuffer, &output.output) {
let _ = device let _ = device
.renderer .renderer
.render( .render(
&mut framebuffer, &mut framebuffer,
device output
.output .output
.current_mode() .current_mode()
.expect("output has a mode") .expect("output has a mode")
.size, .size,
device.output.current_transform(), output.output.current_transform(),
)? )?
.finish()?; .finish()?;
} }
@@ -336,46 +365,48 @@ fn render_device(state: &mut Ferese, node: DrmNode) {
return Ok(false); return Ok(false);
}; };
let mut presentation = OutputPresentationFeedback::new(&device.output); let mut presentation = OutputPresentationFeedback::new(&output.output);
state.space.elements().for_each(|window| { state.space.elements().for_each(|window| {
window.take_presentation_feedback( window.take_presentation_feedback(
&mut presentation, &mut presentation,
|_, _| Some(device.output.clone()), |_, _| Some(output.output.clone()),
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion, |_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
); );
}); });
let layers = layer_map_for_output(&device.output) let layers = layer_map_for_output(&output.output)
.layers() .layers()
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
layers.iter().for_each(|layer| { layers.iter().for_each(|layer| {
layer.take_presentation_feedback( layer.take_presentation_feedback(
&mut presentation, &mut presentation,
|_, _| Some(device.output.clone()), |_, _| Some(output.output.clone()),
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion, |_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
); );
}); });
device output
.surface .surface
.queue_buffer(Some(result.sync), Some(damage), presentation)?; .queue_buffer(Some(result.sync), Some(damage), presentation)?;
device.frame_pending = true; output.frame_pending = true;
Ok(true) Ok(true)
})(); })();
let queued = match rendered { let queued = match rendered {
Ok(true) => { Ok(true) => {
tracing::trace!(?node, "queued DRM frame"); tracing::trace!(?node, ?crtc, "queued DRM frame");
true true
} }
Ok(false) => false, Ok(false) => false,
Err(error) => { Err(error) => {
tracing::error!(?node, %error, "failed to render DRM frame"); tracing::error!(?node, ?crtc, %error, "failed to render DRM frame");
false false
} }
}; };
let callback_output = output.output.clone();
device.outputs.insert(crtc, output);
restore_device(state, node, device); restore_device(state, node, device);
if queued { if queued {
send_frame_callbacks(state); send_frame_callbacks(state, &callback_output);
} }
} }
@@ -385,58 +416,156 @@ fn restore_device(state: &mut Ferese, node: DrmNode, device: DirectDevice) {
} }
} }
fn refresh_duration(state: &Ferese, node: DrmNode) -> Duration { fn direct_node_for_device(state: &Ferese, device_id: libc::dev_t) -> Option<DrmNode> {
state
.direct_backend
.as_ref()?
.devices
.keys()
.copied()
.find(|node| node.dev_id() == device_id)
}
fn rescan_device(state: &mut Ferese, node: DrmNode) {
let Some(mut device) = state
.direct_backend
.as_mut()
.and_then(|backend| backend.devices.remove(&node))
else {
return;
};
let selections = match select_outputs(&device.drm) {
Ok(selections) => selections,
Err(error) if error.kind() == io::ErrorKind::NotFound => Vec::new(),
Err(error) => {
tracing::error!(?node, %error, "failed to scan DRM connectors");
restore_device(state, node, device);
return;
}
};
let mut selections = selections
.into_iter()
.map(|selection @ (_, crtc, _)| (crtc, selection))
.collect::<HashMap<_, _>>();
let existing = device.outputs.keys().copied().collect::<Vec<_>>();
for crtc in existing {
let unchanged = device.outputs.get(&crtc).is_some_and(|output| {
selections.get(&crtc).is_some_and(|(connector, _, mode)| {
connector.handle() == output.connector && *mode == output.mode
})
});
if unchanged {
selections.remove(&crtc);
continue;
}
if let Some(output) = device.outputs.remove(&crtc) {
state.display_handle.disable_global::<Ferese>(output.global);
state.unregister_output(&output.output);
if let Some(backend) = state.direct_backend.as_mut() {
backend.presentation.remove(&(node, crtc));
}
tracing::info!(?node, ?crtc, "removed DRM output");
}
}
for (_, (connector, crtc, mode)) in selections {
match create_direct_output(
state,
&mut device.drm,
&device.gbm,
&device.renderer,
connector,
crtc,
mode,
) {
Ok(output) => {
state
.direct_backend
.as_mut()
.expect("direct backend state remains initialized")
.presentation
.entry((node, crtc))
.or_default()
.set_refresh(OutputMode::from(mode).refresh);
device.outputs.insert(crtc, output);
}
Err(error) => tracing::error!(?node, ?crtc, %error, "failed to add DRM output"),
}
}
restore_device(state, node, device);
state.relayout();
render_all(state);
}
fn remove_device(state: &mut Ferese, node: DrmNode) {
let Some(device) = state
.direct_backend
.as_mut()
.and_then(|backend| backend.devices.remove(&node))
else {
return;
};
for (crtc, output) in device.outputs {
state.display_handle.disable_global::<Ferese>(output.global);
state.unregister_output(&output.output);
if let Some(backend) = state.direct_backend.as_mut() {
backend.presentation.remove(&(node, crtc));
}
}
tracing::info!(?node, "removed DRM device");
}
fn refresh_duration(state: &Ferese, node: DrmNode, crtc: crtc::Handle) -> Duration {
state state
.direct_backend .direct_backend
.as_ref() .as_ref()
.and_then(|backend| backend.devices.get(&node)) .and_then(|backend| backend.devices.get(&node))
.and_then(|device| device.output.current_mode()) .and_then(|device| device.outputs.get(&crtc))
.and_then(|output| output.output.current_mode())
.filter(|mode| mode.refresh > 0) .filter(|mode| mode.refresh > 0)
.map(|mode| Duration::from_nanos(1_000_000_000_000_u64 / mode.refresh as u64)) .map(|mode| Duration::from_nanos(1_000_000_000_000_u64 / mode.refresh as u64))
.unwrap_or_else(|| Duration::from_millis(16)) .unwrap_or_else(|| Duration::from_millis(16))
} }
fn send_frame_callbacks(state: &mut Ferese) { fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
let Some(output) = state
.direct_backend
.as_ref()
.and_then(|backend| backend.devices.values().next())
.map(|device| device.output.clone())
else {
return;
};
state.space.elements().for_each(|window| { state.space.elements().for_each(|window| {
window.send_frame( window.send_frame(
&output, output,
state.start_time.elapsed(), state.start_time.elapsed(),
Some(Duration::ZERO), Some(Duration::ZERO),
|_, _| Some(output.clone()), |_, _| Some(output.clone()),
); );
}); });
let layers = layer_map_for_output(&output) let layers = layer_map_for_output(output)
.layers() .layers()
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
layers.iter().for_each(|layer| { layers.iter().for_each(|layer| {
layer.send_frame( layer.send_frame(
&output, output,
state.start_time.elapsed(), state.start_time.elapsed(),
Some(Duration::ZERO), Some(Duration::ZERO),
|_, _| Some(output.clone()), |_, _| Some(output.clone()),
); );
}); });
state.send_cursor_frame(&output); state.send_cursor_frame(output);
state.space.refresh(); state.space.refresh();
state.popups.cleanup(); state.popups.cleanup();
layer_map_for_output(&output).cleanup(); layer_map_for_output(output).cleanup();
if let Err(error) = state.display_handle.flush_clients() { if let Err(error) = state.display_handle.flush_clients() {
tracing::debug!(%error, "failed to flush clients after DRM frame"); tracing::debug!(%error, "failed to flush clients after DRM frame");
} }
} }
fn select_output(drm: &DrmDevice) -> io::Result<(connector::Info, crtc::Handle, DrmMode)> { fn select_outputs(drm: &DrmDevice) -> io::Result<Vec<(connector::Info, crtc::Handle, DrmMode)>> {
let resources = drm.resource_handles()?; let resources = drm.resource_handles()?;
let mut selections = Vec::new();
let mut used_crtcs = HashSet::new();
for handle in resources.connectors() { for handle in resources.connectors() {
let connector = drm.get_connector(*handle, true)?; let connector = drm.get_connector(*handle, true)?;
@@ -456,26 +585,71 @@ fn select_output(drm: &DrmDevice) -> io::Result<(connector::Info, crtc::Handle,
let current_crtc = connector let current_crtc = connector
.current_encoder() .current_encoder()
.and_then(|handle| drm.get_encoder(handle).ok()) .and_then(|handle| drm.get_encoder(handle).ok())
.and_then(|encoder| encoder.crtc()); .and_then(|encoder| encoder.crtc())
.filter(|crtc| !used_crtcs.contains(crtc));
let compatible_crtc = connector.encoders().iter().find_map(|handle| { let compatible_crtc = connector.encoders().iter().find_map(|handle| {
let encoder = drm.get_encoder(*handle).ok()?; let encoder = drm.get_encoder(*handle).ok()?;
resources resources
.filter_crtcs(encoder.possible_crtcs()) .filter_crtcs(encoder.possible_crtcs())
.into_iter() .into_iter()
.next() .find(|crtc| !used_crtcs.contains(crtc))
}); });
if let Some(crtc) = current_crtc.or(compatible_crtc) { if let Some(crtc) = current_crtc.or(compatible_crtc) {
return Ok((connector, crtc, mode)); used_crtcs.insert(crtc);
selections.push((connector, crtc, mode));
} }
} }
if selections.is_empty() {
Err(io::Error::new( Err(io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
"no connected desktop DRM connector with a usable CRTC", "no connected desktop DRM connector with a usable CRTC",
)) ))
} else {
Ok(selections)
}
} }
fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode) -> Output { fn create_direct_output(
state: &mut Ferese,
drm: &mut DrmDevice,
gbm: &GbmDevice<DrmDeviceFd>,
renderer: &GlesRenderer,
connector: connector::Info,
crtc: crtc::Handle,
mode: DrmMode,
) -> Result<DirectOutput, Box<dyn Error>> {
let (output, global) = create_output(state, &connector, mode);
let drm_surface = drm.create_surface(crtc, mode, &[connector.handle()])?;
let allocator = GbmAllocator::new(
gbm.clone(),
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
);
let surface = GbmBufferedSurface::new(
drm_surface,
allocator,
&[Fourcc::Argb8888, Fourcc::Abgr8888],
renderer.dmabuf_formats(),
)?;
let damage_tracker = OutputDamageTracker::from_output(&output);
tracing::info!(?crtc, connector = %connector, "initialized DRM output");
Ok(DirectOutput {
connector: connector.handle(),
mode,
output,
global,
surface,
damage_tracker,
frame_pending: false,
})
}
fn create_output(
state: &mut Ferese,
connector: &connector::Info,
mode: DrmMode,
) -> (Output, GlobalId) {
let name = connector.to_string(); let name = connector.to_string();
let identity = format!("drm:{name}"); let identity = format!("drm:{name}");
let physical_size = connector.size().unwrap_or((0, 0)); let physical_size = connector.size().unwrap_or((0, 0));
@@ -490,7 +664,7 @@ fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode)
); );
let output_mode = OutputMode::from(mode); let output_mode = OutputMode::from(mode);
output.create_global::<Ferese>(&state.display_handle); let global = output.create_global::<Ferese>(&state.display_handle);
output.set_preferred(output_mode); output.set_preferred(output_mode);
output.change_current_state( output.change_current_state(
Some(output_mode), Some(output_mode),
@@ -507,12 +681,20 @@ fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode)
.unwrap_or(0); .unwrap_or(0);
state.space.map_output(&output, (x, 0)); state.space.map_output(&output, (x, 0));
state.register_output(&output, identity); state.register_output(&output, identity);
output (output, global)
} }
impl DirectBackendState { impl DirectBackendState {
pub fn record_presentation(&mut self, time: DrmEventTime, sequence: u32) -> Option<Duration> { pub fn record_presentation(
self.presentation.record(time, sequence) &mut self,
node: DrmNode,
crtc: crtc::Handle,
time: DrmEventTime,
sequence: u32,
) -> Option<Duration> {
self.presentation
.get_mut(&(node, crtc))
.and_then(|clock| clock.record(time, sequence))
} }
} }
+2 -2
View File
@@ -33,9 +33,9 @@ pub(crate) struct FrameData {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PendingScreencopy { pub(crate) struct PendingScreencopy {
frame: ZwlrScreencopyFrameV1, pub(crate) frame: ZwlrScreencopyFrameV1,
buffer: WlBuffer, buffer: WlBuffer,
output: Output, pub(crate) output: Output,
region: Rectangle<i32, Buffer>, region: Rectangle<i32, Buffer>,
with_damage: bool, with_damage: bool,
} }
+51 -3
View File
@@ -11,7 +11,11 @@ use ferese_core::{OutputGeometry, OutputId, OutputWorkspaceMap, WindowPlacement,
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId}; use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
use smithay::{ use smithay::{
backend::{allocator::dmabuf::Dmabuf, drm::DrmEventTime, renderer::ImportDma}, backend::{
allocator::dmabuf::Dmabuf,
drm::{DrmEventTime, DrmNode},
renderer::ImportDma,
},
desktop::{ desktop::{
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output, LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
utils::send_frames_surface_tree, utils::send_frames_surface_tree,
@@ -23,6 +27,7 @@ use smithay::{
output::Output, output::Output,
reexports::{ reexports::{
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic}, calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
drm::control::crtc,
wayland_protocols::xdg::{ wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode, decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode,
shell::server::xdg_toplevel, shell::server::xdg_toplevel,
@@ -158,6 +163,43 @@ impl Ferese {
} }
} }
pub fn unregister_output(&mut self, output: &Output) {
let Some(output_id) = self.output_ids.remove(output) else {
return;
};
self.pending_screencopies.retain(|capture| {
if capture.output == *output {
capture.frame.failed();
false
} else {
true
}
});
self.space.unmap_output(output);
match self.output_workspaces.disconnect(output_id) {
Ok(Some(target)) => {
if let Some(workspace) = self.output_workspaces.active_workspace(target) {
self.activate_output_workspace(target, workspace);
}
}
Ok(None) => {
self.focused_window = None;
self.restore_keyboard_focus();
}
Err(error) => {
tracing::error!(%error, output = %output.name(), "failed to unregister output")
}
}
self.relayout();
}
pub(crate) fn is_focused_output(&self, output: &Output) -> bool {
self.output_ids.get(output).copied() == self.output_workspaces.focused_output()
}
fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) { fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
if let Err(error) = self.output_workspaces.focus_output(output) { if let Err(error) = self.output_workspaces.focus_output(output) {
tracing::error!(%error, "failed to focus output"); tracing::error!(%error, "failed to focus output");
@@ -672,11 +714,17 @@ impl Ferese {
self.advance_animations_by(delta) self.advance_animations_by(delta)
} }
pub fn record_drm_presentation(&mut self, time: DrmEventTime, sequence: u32) { pub fn record_drm_presentation(
&mut self,
node: DrmNode,
crtc: crtc::Handle,
time: DrmEventTime,
sequence: u32,
) {
let delta = self let delta = self
.direct_backend .direct_backend
.as_mut() .as_mut()
.and_then(|backend| backend.record_presentation(time, sequence)); .and_then(|backend| backend.record_presentation(node, crtc, time, sequence));
if let Some(delta) = delta { if let Some(delta) = delta {
self.advance_animations_by(delta); self.advance_animations_by(delta);
} }