Fix compositor output routing and pointer hints; format imports

This commit is contained in:
Samuel Onoja committed 2026-09-30 06:39:58 +01:00
1 parent 88e4f3c135
commit 46509f880a
108 files changed
+3682 -8371

No files matched your search

+113 -281
View File
@@ -1,57 +1,41 @@
use std::{
collections::{HashMap, HashSet},
error::Error,
io,
path::Path,
time::{Duration, Instant},
};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::io;
use std::path::Path;
use std::time::{Duration, Instant};
use smithay::{
backend::{
allocator::{
Fourcc,
gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
},
drm::{
DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType,
},
egl::{EGLContext, EGLDisplay},
input::InputEvent,
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
Bind, Frame, ImportDma, ImportMemWl, Renderer, damage::OutputDamageTracker,
gles::GlesRenderer,
},
session::{Event as SessionEvent, Session, libseat::LibSeatSession},
udev::{UdevBackend, UdevEvent, primary_gpu},
},
desktop::{layer_map_for_output, utils::OutputPresentationFeedback},
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
reexports::{
calloop::{
EventLoop, RegistrationToken,
timer::{TimeoutAction, Timer},
},
drm::control::{
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
},
input::{Device as LibinputDevice, Libinput},
rustix::fs::OFlags,
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
wayland_server::backend::GlobalId,
},
utils::{DeviceFd, Monotonic, Time, Transform},
wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh},
use smithay::backend::allocator::Fourcc;
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType};
use smithay::backend::egl::{EGLContext, EGLDisplay};
use smithay::backend::input::InputEvent;
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::{Bind, Frame, ImportDma, ImportMemWl, Renderer};
use smithay::backend::session::libseat::LibSeatSession;
use smithay::backend::session::{Event as SessionEvent, Session};
use smithay::backend::udev::{UdevBackend, UdevEvent, primary_gpu};
use smithay::desktop::layer_map_for_output;
use smithay::desktop::utils::OutputPresentationFeedback;
use smithay::output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel};
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
use smithay::reexports::calloop::{EventLoop, RegistrationToken};
use smithay::reexports::drm::control::{
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
};
use smithay::reexports::input::{Device as LibinputDevice, Libinput};
use smithay::reexports::rustix::fs::OFlags;
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;
use smithay::reexports::wayland_server::backend::GlobalId;
use smithay::utils::{DeviceFd, Monotonic, Time, Transform};
use smithay::wayland::dmabuf::DmabufFeedbackBuilder;
use smithay::wayland::presentation::Refresh;
use crate::{
Ferese,
config::{OutputModeRequest, OutputProfile, OutputSettings, OutputTransform},
metrics::RenderMetrics,
winit::{
animated_window_elements, cursorless_window_elements, frame_effect_metrics, redraw_output,
},
};
use crate::Ferese;
use crate::config::{OutputModeRequest, OutputProfile, OutputSettings, OutputTransform};
use crate::metrics::RenderMetrics;
use crate::winit::{animated_window_elements, cursorless_window_elements, frame_effect_metrics, redraw_output};
pub struct DirectBackendState {
pub session: LibSeatSession,
@@ -72,14 +56,12 @@ impl DirectBackendState {
geometry: smithay::utils::Rectangle<i32, smithay::utils::Logical>,
output: &Output,
remaining: usize,
) -> Result<Option<crate::winit::ResizeSnapshot>, smithay::backend::renderer::gles::GlesError>
{
let Some(device) = self.devices.values_mut().find(|device| {
device
.outputs
.values()
.any(|candidate| &candidate.output == output)
}) else {
) -> Result<Option<crate::winit::ResizeSnapshot>, smithay::backend::renderer::gles::GlesError> {
let Some(device) = self
.devices
.values_mut()
.find(|device| device.outputs.values().any(|candidate| &candidate.output == output))
else {
return Ok(None);
};
crate::winit::capture_resize_snapshot(
@@ -100,12 +82,7 @@ impl DirectBackendState {
let device = self
.devices
.values_mut()
.find(|device| {
device
.outputs
.values()
.any(|candidate| &candidate.output == output)
})
.find(|device| device.outputs.values().any(|candidate| &candidate.output == output))
.ok_or("Window output is unavailable")?;
crate::winit::capture_window_buffer(
&mut device.renderer,
@@ -124,12 +101,7 @@ impl DirectBackendState {
let device = self
.devices
.values_mut()
.find(|device| {
device
.outputs
.values()
.any(|candidate| &candidate.output == output)
})
.find(|device| device.outputs.values().any(|candidate| &candidate.output == output))
.ok_or("Window output is unavailable")?;
crate::winit::capture_window_frame(
&mut device.renderer,
@@ -212,12 +184,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
let seat_name = session.seat();
let udev_backend = UdevBackend::new(&seat_name)?;
let primary_path = primary_gpu(&seat_name)?
.or_else(|| {
udev_backend
.device_list()
.next()
.map(|(_, path)| path.to_owned())
})
.or_else(|| udev_backend.device_list().next().map(|(_, path)| path.to_owned()))
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no DRM device found"))?;
let mut libinput_context =
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
@@ -254,9 +221,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
}
InputEvent::DeviceRemoved { device } => {
if let Some(backend) = state.direct_backend.as_mut() {
backend
.input_devices
.retain(|candidate| candidate != &device);
backend.input_devices.retain(|candidate| candidate != &device);
}
}
event => state.process_input_event(event),
@@ -275,8 +240,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
device.drm.pause();
device.outputs.values_mut().for_each(|output| {
output.surface.reset_buffers();
output.damage_tracker =
OutputDamageTracker::from_output(&output.output);
output.damage_tracker = OutputDamageTracker::from_output(&output.output);
output.frame_pending = false;
output.power_off = false;
});
@@ -335,20 +299,12 @@ fn configure_libinput_device(state: &Ferese, device: &mut LibinputDevice) {
if device.config_tap_finger_count() > 0
&& let Err(error) = device.config_tap_set_enabled(settings.tap)
{
tracing::warn!(
device = device.name(),
?error,
"failed to configure tap-to-click"
);
tracing::warn!(device = device.name(), ?error, "failed to configure tap-to-click");
}
if device.config_scroll_has_natural_scroll()
&& let Err(error) = device.config_scroll_set_natural_scroll_enabled(settings.natural_scroll)
{
tracing::warn!(
device = device.name(),
?error,
"failed to configure natural scrolling"
);
tracing::warn!(device = device.name(), ?error, "failed to configure natural scrolling");
}
if device.config_dwt_is_available()
&& let Err(error) = device.config_dwt_set_enabled(settings.disable_while_typing)
@@ -383,10 +339,7 @@ pub(crate) fn reload_outputs(state: &mut Ferese) {
}
}
pub(crate) fn validate_live_outputs(
state: &Ferese,
profiles: &[OutputProfile],
) -> Result<(), String> {
pub(crate) fn validate_live_outputs(state: &Ferese, profiles: &[OutputProfile]) -> Result<(), String> {
let Some(backend) = state.direct_backend.as_ref() else {
return Ok(());
};
@@ -414,10 +367,7 @@ fn open_primary_device(
.session
.clone();
let node = DrmNode::from_path(path)?;
let fd = session.open(
path,
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
)?;
let fd = session.open(path, OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK)?;
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
let (mut drm, notifier) = DrmDevice::new(fd.clone(), true)?;
let gbm = GbmDevice::new(fd)?;
@@ -443,10 +393,7 @@ fn open_primary_device(
let mut scan = select_outputs(&drm, &state.output_profiles)?;
apply_lid_policy(
&mut scan,
state
.direct_backend
.as_ref()
.is_some_and(|backend| backend.lid_closed),
state.direct_backend.as_ref().is_some_and(|backend| backend.lid_closed),
);
if scan.selections.is_empty() {
return Err(io::Error::new(
@@ -468,9 +415,7 @@ fn open_primary_device(
mode,
settings,
} = selection;
let output = create_direct_output(
state, &mut drm, &gbm, &renderer, connector, crtc, mode, &settings,
)?;
let output = create_direct_output(state, &mut drm, &gbm, &renderer, connector, crtc, mode, &settings)?;
state
.direct_backend
.as_mut()
@@ -503,9 +448,7 @@ fn open_primary_device(
None
}
});
if let (Some(mut feedback), DrmEventTime::Monotonic(time)) =
(feedback, metadata.time)
{
if let (Some(mut feedback), DrmEventTime::Monotonic(time)) = (feedback, metadata.time) {
feedback.presented(
Time::<Monotonic>::from(time),
Refresh::fixed(refresh_duration(state, node, crtc)),
@@ -538,10 +481,7 @@ fn open_primary_device(
}
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
let active = state
.direct_backend
.as_ref()
.is_some_and(|backend| backend.active);
let active = state.direct_backend.as_ref().is_some_and(|backend| backend.active);
if active {
let was_animating = state
.direct_backend
@@ -588,11 +528,7 @@ pub(crate) fn sleep_locked_outputs(state: &mut Ferese) {
let Some(backend) = state.direct_backend.as_mut() else {
return;
};
for device in backend
.devices
.values_mut()
.filter(|device| device.drm.is_active())
{
for device in backend.devices.values_mut().filter(|device| device.drm.is_active()) {
for output in device.outputs.values_mut() {
if output.power_off || output.frame_pending {
continue;
@@ -621,11 +557,7 @@ pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
}
pub fn render_all(state: &mut Ferese) {
if state
.direct_backend
.as_ref()
.is_none_or(|backend| !backend.active)
{
if state.direct_backend.as_ref().is_none_or(|backend| !backend.active) {
return;
}
let animating = state.advance_animations(Instant::now());
@@ -692,9 +624,7 @@ fn initial_lid_closed() -> bool {
fn internal_connector(interface: connector::Interface) -> bool {
matches!(
interface,
connector::Interface::EmbeddedDisplayPort
| connector::Interface::LVDS
| connector::Interface::DSI
connector::Interface::EmbeddedDisplayPort | connector::Interface::LVDS | connector::Interface::DSI
)
}
@@ -767,8 +697,7 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
state.process_dmabuf_imports(&mut device.renderer);
let (mut buffer, age) = output.surface.next_buffer()?;
let elements = animated_window_elements(state, &mut device.renderer, &output.output);
let effects =
frame_effect_metrics(&elements, output.output.current_scale().fractional_scale());
let effects = frame_effect_metrics(&elements, output.output.current_scale().fractional_scale());
let mut framebuffer = device.renderer.bind(&mut buffer)?;
let result = output.damage_tracker.render_output(
&mut device.renderer,
@@ -778,12 +707,10 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
[0.035, 0.04, 0.055, 1.0],
)?;
let cursorless_capture = state.has_pending_screencopy(&output.output, false);
let captured_with_cursor =
state.process_screencopies(&mut device.renderer, &framebuffer, &output.output, true);
let captured_with_cursor = state.process_screencopies(&mut device.renderer, &framebuffer, &output.output, true);
if cursorless_capture {
let cursorless_elements =
cursorless_window_elements(state, &mut device.renderer, &output.output);
let cursorless_elements = cursorless_window_elements(state, &mut device.renderer, &output.output);
redraw_output(
&mut device.renderer,
&mut framebuffer,
@@ -791,22 +718,13 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
&cursorless_elements,
)?;
state.process_screencopies(&mut device.renderer, &framebuffer, &output.output, false);
redraw_output(
&mut device.renderer,
&mut framebuffer,
&output.output,
&elements,
)?;
redraw_output(&mut device.renderer, &mut framebuffer, &output.output, &elements)?;
} else if captured_with_cursor {
let _ = device
.renderer
.render(
&mut framebuffer,
output
.output
.current_mode()
.expect("output has a mode")
.size,
output.output.current_mode().expect("output has a mode").size,
output.output.current_transform(),
)?
.finish()?;
@@ -818,34 +736,18 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
return Ok(false);
};
let mut presentation = OutputPresentationFeedback::new(&output.output);
state.space.elements().for_each(|window| {
window.take_presentation_feedback(
&mut presentation,
|_, _| Some(output.output.clone()),
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
);
});
let layers = layer_map_for_output(&output.output)
.layers()
.cloned()
.collect::<Vec<_>>();
layers.iter().for_each(|layer| {
layer.take_presentation_feedback(
&mut presentation,
|_, _| Some(output.output.clone()),
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
);
});
let presentation = crate::presentation::take_output_feedback(
state,
&output.output,
&result.states,
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
);
output
.surface
.queue_buffer(Some(result.sync), Some(damage.clone()), presentation)?;
output.render_metrics.record_frame(
render_started.elapsed(),
&damage,
missed_deadlines,
effects,
);
output
.render_metrics
.record_frame(render_started.elapsed(), &damage, missed_deadlines, effects);
output.frame_pending = true;
output.lock_frame_pending = state.session_lock.active;
Ok(true)
@@ -893,10 +795,7 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) {
return;
}
};
let lid_closed = state
.direct_backend
.as_ref()
.is_some_and(|backend| backend.lid_closed);
let lid_closed = state.direct_backend.as_ref().is_some_and(|backend| backend.lid_closed);
apply_lid_policy(&mut scan, lid_closed);
if let Some(backend) = state.direct_backend.as_mut() {
backend.connected_outputs = scan.connected_outputs;
@@ -940,9 +839,7 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) {
Some(Scale::Fractional(output.settings.scale)),
Some((position[0], position[1]).into()),
);
state
.space
.map_output(&output.output, (position[0], position[1]));
state.space.map_output(&output.output, (position[0], position[1]));
if let Some(id) = state.output_id(&output.output)
&& let Some(g) = state.space.output_geometry(&output.output)
{
@@ -1045,10 +942,7 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) {
}
for crtc in deferred_removals {
let keep_internal = device
.outputs
.get(&crtc)
.is_some_and(|output| output.internal)
let keep_internal = device.outputs.get(&crtc).is_some_and(|output| output.internal)
&& lid_closed
&& state.direct_backend.as_ref().is_some_and(|backend| {
device.outputs.get(&crtc).is_some_and(|output| {
@@ -1060,10 +954,7 @@ fn rescan_device(state: &mut Ferese, node: DrmNode) {
})
&& !device.outputs.values().any(|output| !output.internal);
if keep_internal {
tracing::warn!(
?crtc,
"keeping laptop panel enabled: external output activation failed"
);
tracing::warn!(?crtc, "keeping laptop panel enabled: external output activation failed");
if let Some(output) = device.outputs.get(&crtc)
&& let Some(backend) = state.direct_backend.as_mut()
&& let Some(info) = backend
@@ -1116,9 +1007,7 @@ fn remove_device(state: &mut Ferese, node: DrmNode) {
let context = device.renderer.context_id().erased();
state.wallpaper.forget_context(&context);
state
.resize_snapshots
.retain(|_, snapshot| snapshot.context != context);
state.resize_snapshots.retain(|_, snapshot| snapshot.context != context);
for (crtc, output) in device.outputs {
state.display_handle.disable_global::<Ferese>(output.global);
state.unregister_output(&output.output);
@@ -1170,9 +1059,10 @@ fn arm_animation_timer(state: &mut Ferese) {
match state
.loop_handle
.insert_source(Timer::from_deadline(deadline), |_, _, state| {
let active = state.direct_backend.as_ref().is_some_and(|backend| {
backend.active && backend.animation_active && !state.session_lock.sleeping
});
let active = state
.direct_backend
.as_ref()
.is_some_and(|backend| backend.active && backend.animation_active && !state.session_lock.sleeping);
if active {
let deadline = state.animation_fallback_deadline(animation_interval(state));
if Instant::now() < deadline {
@@ -1184,9 +1074,7 @@ fn arm_animation_timer(state: &mut Ferese) {
.as_ref()
.is_some_and(|backend| backend.animation_active)
{
return TimeoutAction::ToInstant(
state.animation_fallback_deadline(animation_interval(state)),
);
return TimeoutAction::ToInstant(state.animation_fallback_deadline(animation_interval(state)));
}
}
if let Some(backend) = state.direct_backend.as_mut() {
@@ -1244,12 +1132,7 @@ fn schedule_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handl
}
}
fn deliver_frame_callbacks(
state: &mut Ferese,
node: DrmNode,
crtc: crtc::Handle,
identity: &Output,
) {
fn deliver_frame_callbacks(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle, identity: &Output) {
let Some(backend) = state.direct_backend.as_mut() else {
return;
};
@@ -1286,24 +1169,15 @@ fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
.is_some_and(|id| state.window_belongs_to_output(*id, output))
})
.for_each(|window| {
window.send_frame(
output,
state.start_time.elapsed(),
Some(Duration::ZERO),
|_, _| Some(output.clone()),
);
window.send_frame(output, state.start_time.elapsed(), Some(Duration::ZERO), |_, _| {
Some(output.clone())
});
});
let layers = layer_map_for_output(output)
.layers()
.cloned()
.collect::<Vec<_>>();
let layers = layer_map_for_output(output).layers().cloned().collect::<Vec<_>>();
layers.iter().for_each(|layer| {
layer.send_frame(
output,
state.start_time.elapsed(),
Some(Duration::ZERO),
|_, _| Some(output.clone()),
);
layer.send_frame(output, state.start_time.elapsed(), Some(Duration::ZERO), |_, _| {
Some(output.clone())
});
});
state.send_cursor_frame(output);
state.space.refresh();
@@ -1365,12 +1239,7 @@ fn select_outputs(drm: &DrmDevice, profiles: &[OutputProfile]) -> io::Result<Out
profile: active_profile.map(|profile| profile.name.clone()),
physical_size: connector.size(),
current_mode: selected_mode.map(connected_mode_info),
available_modes: connector
.modes()
.iter()
.copied()
.map(connected_mode_info)
.collect(),
available_modes: connector.modes().iter().copied().map(connected_mode_info).collect(),
scale: settings.scale,
transform: settings.transform,
configured_position: settings.position,
@@ -1438,10 +1307,7 @@ fn default_output_settings(matcher: String) -> OutputSettings {
}
}
fn select_mode(
connector: &connector::Info,
requested: Option<OutputModeRequest>,
) -> Option<DrmMode> {
fn select_mode(connector: &connector::Info, requested: Option<OutputModeRequest>) -> Option<DrmMode> {
let fallback = || {
connector
.modes()
@@ -1460,24 +1326,11 @@ fn select_mode(
.filter(|mode| mode.size() == (requested.width, requested.height));
let selected = if let Some(refresh) = requested.refresh_millihertz {
matching
.min_by_key(|mode| {
OutputMode::from(**mode)
.refresh
.unsigned_abs()
.abs_diff(refresh)
})
.filter(|mode| {
OutputMode::from(**mode)
.refresh
.unsigned_abs()
.abs_diff(refresh)
<= 1_000
})
.min_by_key(|mode| OutputMode::from(**mode).refresh.unsigned_abs().abs_diff(refresh))
.filter(|mode| OutputMode::from(**mode).refresh.unsigned_abs().abs_diff(refresh) <= 1_000)
.copied()
} else {
matching
.max_by_key(|mode| OutputMode::from(**mode).refresh)
.copied()
matching.max_by_key(|mode| OutputMode::from(**mode).refresh).copied()
};
if selected.is_none() {
tracing::warn!(
@@ -1504,10 +1357,7 @@ fn create_direct_output(
) -> Result<DirectOutput, Box<dyn Error>> {
let identity = connector_identity(drm, &connector);
let drm_surface = drm.create_surface(crtc, mode, &[connector.handle()])?;
let allocator = GbmAllocator::new(
gbm.clone(),
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
);
let allocator = GbmAllocator::new(gbm.clone(), GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT);
let surface = GbmBufferedSurface::new(
drm_surface,
allocator,
@@ -1602,21 +1452,18 @@ fn output_transform(transform: OutputTransform) -> Transform {
}
fn connector_identity(drm: &DrmDevice, connector: &connector::Info) -> String {
let edid = drm
.get_properties(connector.handle())
.ok()
.and_then(|properties| {
properties.iter().find_map(|(handle, raw)| {
let info = drm.get_property(*handle).ok()?;
if info.name().to_bytes() != b"EDID" {
return None;
}
match info.value_type().convert_value(*raw) {
property::Value::Blob(blob) if blob != 0 => drm.get_property_blob(blob).ok(),
_ => None,
}
})
});
let edid = drm.get_properties(connector.handle()).ok().and_then(|properties| {
properties.iter().find_map(|(handle, raw)| {
let info = drm.get_property(*handle).ok()?;
if info.name().to_bytes() != b"EDID" {
return None;
}
match info.value_type().convert_value(*raw) {
property::Value::Blob(blob) if blob != 0 => drm.get_property_blob(blob).ok(),
_ => None,
}
})
});
match edid {
Some(edid) if !edid.is_empty() => format!("drm-edid:{:016x}", stable_hash(&edid)),
@@ -1654,23 +1501,16 @@ impl PresentationClock {
fn set_refresh(&mut self, refresh_millihertz: i32) {
if refresh_millihertz > 0 {
self.refresh_interval = Some(Duration::from_nanos(
1_000_000_000_000_u64 / refresh_millihertz as u64,
));
self.refresh_interval = Some(Duration::from_nanos(1_000_000_000_000_u64 / refresh_millihertz as u64));
}
}
fn record(&mut self, time: DrmEventTime, sequence: u32) -> Option<Duration> {
let DrmEventTime::Monotonic(time) = time else {
tracing::warn!(
sequence,
"DRM driver reported a realtime page-flip timestamp"
);
tracing::warn!(sequence, "DRM driver reported a realtime page-flip timestamp");
return None;
};
let delta = self
.last_presentation
.map(|previous| time.saturating_sub(previous));
let delta = self.last_presentation.map(|previous| time.saturating_sub(previous));
self.last_presentation = Some(time);
self.presented_frames = self.presented_frames.saturating_add(1);
@@ -1709,9 +1549,7 @@ mod tests {
#[test]
fn panel_detection_uses_connector_type_not_monitor_name() {
assert!(internal_connector(
connector::Interface::EmbeddedDisplayPort
));
assert!(internal_connector(connector::Interface::EmbeddedDisplayPort));
assert!(internal_connector(connector::Interface::LVDS));
assert!(internal_connector(connector::Interface::DSI));
assert!(!internal_connector(connector::Interface::HDMIA));
@@ -1744,10 +1582,7 @@ mod tests {
fn presentation_clock_rejects_realtime_timestamps() {
let mut clock = PresentationClock::default();
assert_eq!(
clock.record(DrmEventTime::Realtime(SystemTime::now()), 1),
None
);
assert_eq!(clock.record(DrmEventTime::Realtime(SystemTime::now()), 1), None);
assert_eq!(clock.presented_frames, 0);
}
@@ -1770,10 +1605,7 @@ mod tests {
clock.reset_timing();
assert_eq!(
clock.record(DrmEventTime::Monotonic(Duration::from_secs(10)), 2),
None
);
assert_eq!(clock.record(DrmEventTime::Monotonic(Duration::from_secs(10)), 2), None);
assert_eq!(clock.presented_frames, 2);
assert_eq!(clock.missed_deadlines, 0);
}
+10 -33
View File
@@ -4,7 +4,8 @@ use std::ffi::OsString;
use smithay::reexports::calloop::EventLoop;
use crate::{Ferese, private_client::ClientCapabilities};
use crate::Ferese;
use crate::private_client::ClientCapabilities;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BackendKind {
@@ -21,16 +22,12 @@ pub struct LaunchConfig {
impl LaunchConfig {
pub fn from_environment() -> Result<Self, String> {
let graphical_session =
std::env::var_os("WAYLAND_DISPLAY").is_some() || std::env::var_os("DISPLAY").is_some();
let graphical_session = std::env::var_os("WAYLAND_DISPLAY").is_some() || std::env::var_os("DISPLAY").is_some();
Self::parse(std::env::args_os().skip(1), graphical_session)
}
fn parse(
args: impl IntoIterator<Item = OsString>,
graphical_session: bool,
) -> Result<Self, String> {
fn parse(args: impl IntoIterator<Item = OsString>, graphical_session: bool) -> Result<Self, String> {
let mut args = args.into_iter().peekable();
let mut requested_backend = None;
let mut client = Vec::new();
@@ -99,10 +96,7 @@ fn parse_backend(value: &OsString) -> Result<Option<BackendKind>, String> {
Some("auto") => Ok(None),
Some("nested") => Ok(Some(BackendKind::Nested)),
Some("drm") => Ok(Some(BackendKind::Drm)),
_ => Err(format!(
"unknown backend {:?}; expected auto, nested, or drm",
value
)),
_ => Err(format!("unknown backend {:?}; expected auto, nested, or drm", value)),
}
}
@@ -161,42 +155,25 @@ mod tests {
.unwrap();
assert_eq!(config.client, [OsString::from("ferese-effects-probe")]);
assert!(
config
.client_capabilities
.contains(ClientCapabilities::EFFECTS)
);
assert!(config.client_capabilities.contains(ClientCapabilities::EFFECTS));
}
#[test]
fn explicitly_grants_shell_control_to_the_launched_private_client() {
let config = LaunchConfig::parse(
[
OsString::from("--grant-shell-control"),
OsString::from("ferese-shell"),
],
[OsString::from("--grant-shell-control"), OsString::from("ferese-shell")],
true,
)
.unwrap();
assert_eq!(config.client, [OsString::from("ferese-shell")]);
assert!(
config
.client_capabilities
.contains(ClientCapabilities::SHELL_CONTROL)
);
assert!(
!config
.client_capabilities
.contains(ClientCapabilities::EFFECTS)
);
assert!(config.client_capabilities.contains(ClientCapabilities::SHELL_CONTROL));
assert!(!config.client_capabilities.contains(ClientCapabilities::EFFECTS));
}
#[test]
fn rejects_an_unknown_backend() {
let error =
LaunchConfig::parse([OsString::from("--backend"), OsString::from("other")], true)
.unwrap_err();
let error = LaunchConfig::parse([OsString::from("--backend"), OsString::from("other")], true).unwrap_err();
assert!(error.contains("unknown backend"));
}