Format Rust sources at 100 columns

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent 4661f595f8
commit 610478f8c7
336 files changed
+23497 -7892

No files matched your search

+57 -15
View File
@@ -40,7 +40,9 @@ impl<C: PartialEq> InputHistory<C> {
fn validate(&self, serial: Serial, client: &C, now: Instant) -> Option<Instant> {
self.events.iter().rev().find_map(|(event, owner, time)| {
(*event == serial && owner == client && now.saturating_duration_since(*time) <= TOKEN_MAX_AGE)
(*event == serial
&& owner == client
&& now.saturating_duration_since(*time) <= TOKEN_MAX_AGE)
.then_some(*time)
})
}
@@ -55,7 +57,8 @@ impl Ferese {
}
if let Some(client) = surface.and_then(|surface| surface.client()) {
self.activation_inputs.record(serial, client.id(), Instant::now());
self.activation_inputs
.record(serial, client.id(), Instant::now());
}
}
}
@@ -86,14 +89,23 @@ impl XdgActivationHandler for Ferese {
return false;
}
let Some(time) = self.activation_inputs.validate(*serial, client, Instant::now()) else {
let Some(time) = self
.activation_inputs
.validate(*serial, client, Instant::now())
else {
return false;
};
data.user_data.insert_if_missing(|| ApprovedInteraction(time));
data.user_data
.insert_if_missing(|| ApprovedInteraction(time));
true
}
fn request_activation(&mut self, token: XdgActivationToken, data: XdgActivationTokenData, surface: WlSurface) {
fn request_activation(
&mut self,
token: XdgActivationToken,
data: XdgActivationTokenData,
surface: WlSurface,
) {
self.xdg_activation_state.remove_token(&token);
if self.session_lock.active() || self.input_capture.captures(1) {
return;
@@ -152,7 +164,9 @@ mod tests {
wire.write_all(&bytes).unwrap();
};
send(&mut wire, 1, 1, &[2]); // wl_display.get_registry
events.dispatch(Duration::from_millis(1), &mut state).unwrap();
events
.dispatch(Duration::from_millis(1), &mut state)
.unwrap();
state.display_handle.flush_clients().unwrap();
wire.set_nonblocking(true).unwrap();
let mut globals = Vec::new();
@@ -162,10 +176,16 @@ mod tests {
let mut seat_name = None;
while offset < globals.len() {
let header = u32::from_ne_bytes(globals[offset + 4..offset + 8].try_into().unwrap());
if u32::from_ne_bytes(globals[offset..offset + 4].try_into().unwrap()) == 2 && header & 0xffff == 0 {
let length = u32::from_ne_bytes(globals[offset + 12..offset + 16].try_into().unwrap()) as usize;
if u32::from_ne_bytes(globals[offset..offset + 4].try_into().unwrap()) == 2
&& header & 0xffff == 0
{
let length =
u32::from_ne_bytes(globals[offset + 12..offset + 16].try_into().unwrap())
as usize;
if &globals[offset + 16..offset + 16 + length] == b"wl_seat\0" {
seat_name = Some(u32::from_ne_bytes(globals[offset + 8..offset + 12].try_into().unwrap()));
seat_name = Some(u32::from_ne_bytes(
globals[offset + 8..offset + 12].try_into().unwrap(),
));
}
}
@@ -180,7 +200,9 @@ mod tests {
);
args.extend([7, 3]);
send(&mut wire, 2, 0, &args);
events.dispatch(Duration::from_millis(1), &mut state).unwrap();
events
.dispatch(Duration::from_millis(1), &mut state)
.unwrap();
let seat: WlSeat = state
.seat
.client_seats(&launcher)
@@ -208,7 +230,9 @@ mod tests {
!state.token_created(token.clone(), data()),
"fresh token renewed stale input"
);
state.activation_inputs.record(serial, launcher.id(), Instant::now());
state
.activation_inputs
.record(serial, launcher.id(), Instant::now());
let approved = data();
assert!(state.token_created(token.clone(), approved.clone()));
assert!(approved.user_data.get::<ApprovedInteraction>().is_some());
@@ -227,7 +251,15 @@ mod tests {
// The proof is attached to the token, not the recipient identity. This
// is what lets a validated launcher hand it to a different application.
assert_ne!(approved.client_id, Some(recipient.id()));
assert!(approved.user_data.get::<ApprovedInteraction>().unwrap().0.elapsed() < TOKEN_MAX_AGE);
assert!(
approved
.user_data
.get::<ApprovedInteraction>()
.unwrap()
.0
.elapsed()
< TOKEN_MAX_AGE
);
// A valid handoff must not focus a recipient before its first buffer.
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_wm_base::XdgWmBase;
@@ -241,12 +273,18 @@ mod tests {
)
.unwrap();
let shell = recipient
.create_resource::<XdgWmBase, XdgWmBaseUserData, Ferese>(&state.display_handle, 6, Default::default())
.create_resource::<XdgWmBase, XdgWmBaseUserData, Ferese>(
&state.display_handle,
6,
Default::default(),
)
.unwrap();
send(&mut recipient_wire, compositor.id().protocol_id(), 0, &[2]);
send(&mut recipient_wire, shell.id().protocol_id(), 2, &[3, 2]);
send(&mut recipient_wire, 3, 1, &[4]);
events.dispatch(Duration::from_millis(1), &mut state).unwrap();
events
.dispatch(Duration::from_millis(1), &mut state)
.unwrap();
let target = state
.space
.elements()
@@ -272,7 +310,11 @@ mod tests {
assert_eq!(history.validate(Serial::from(13), &"launcher", now), None);
assert_eq!(history.validate(serial, &"other-client", now), None);
assert_eq!(
history.validate(serial, &"launcher", now + TOKEN_MAX_AGE + Duration::from_millis(1)),
history.validate(
serial,
&"launcher",
now + TOKEN_MAX_AGE + Duration::from_millis(1)
),
None
);
// An approved token retains the interaction time across client handoff.
+2 -1
View File
@@ -5,7 +5,8 @@ use smithay::reexports::wayland_server::protocol::wl_buffer;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::compositor::{
CompositorClientState, CompositorHandler, CompositorState, get_parent, get_role, is_sync_subsurface,
CompositorClientState, CompositorHandler, CompositorState, get_parent, get_role,
is_sync_subsurface,
};
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
use smithay::wayland::shm::{ShmHandler, ShmState};
+2 -1
View File
@@ -1,6 +1,7 @@
use crate::Ferese;
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, backend::ClientId, protocol as wl,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, backend::ClientId,
protocol as wl,
};
use smithay::reexports::{
wayland_protocols as protocols, wayland_protocols_misc as misc, wayland_protocols_wlr as wlr,
+3 -1
View File
@@ -5,7 +5,9 @@ use smithay::reexports::wayland_protocols_wlr::gamma_control::v1::server::{
zwlr_gamma_control_manager_v1::{self, ZwlrGammaControlManagerV1},
zwlr_gamma_control_v1::{self, ZwlrGammaControlV1},
};
use smithay::reexports::wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource};
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use crate::{Ferese, backends::direct::gamma, gamma::Ramp};
+8 -2
View File
@@ -7,7 +7,10 @@ use crate::Ferese;
impl InputMethodHandler for Ferese {
fn new_popup(&mut self, surface: PopupSurface) {
if let Err(error) = self.popups.track_popup(PopupKind::InputMethod(surface.clone())) {
if let Err(error) = self
.popups
.track_popup(PopupKind::InputMethod(surface.clone()))
{
tracing::warn!(?error, "failed to track input method popup");
}
crate::backends::direct::render_surface(self, surface.wl_surface());
@@ -34,7 +37,10 @@ impl InputMethodHandler for Ferese {
return geometry;
}
let Some(layer) = self.space.layer_for_surface(parent, WindowSurfaceType::TOPLEVEL) else {
let Some(layer) = self
.space
.layer_for_surface(parent, WindowSurfaceType::TOPLEVEL)
else {
return Rectangle::default();
};
+56 -22
View File
@@ -1,4 +1,6 @@
use smithay::desktop::{LayerSurface as DesktopLayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output};
use smithay::desktop::{
LayerSurface as DesktopLayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output,
};
use smithay::output::Output;
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@@ -14,7 +16,13 @@ impl WlrLayerShellHandler for Ferese {
&mut self.layer_shell_state
}
fn new_layer_surface(&mut self, surface: LayerSurface, output: Option<WlOutput>, _layer: Layer, namespace: String) {
fn new_layer_surface(
&mut self,
surface: LayerSurface,
output: Option<WlOutput>,
_layer: Layer,
namespace: String,
) {
// A wl_output resource can outlive its registered desktop output.
// Explicit requests retain their identity; only unspecified requests
// may use the focused/default output policy.
@@ -82,7 +90,11 @@ impl WlrLayerShellHandler for Ferese {
let outputs = self
.space
.outputs()
.filter(|output| layer_map_for_output(output).layer_geometry(&layer).is_some())
.filter(|output| {
layer_map_for_output(output)
.layer_geometry(&layer)
.is_some()
})
.cloned()
.collect::<Vec<_>>();
@@ -134,13 +146,20 @@ impl LayoutState {
}
pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
let Some(layer) = state.space.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL) else {
let Some(layer) = state
.space
.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
else {
return;
};
let outputs = state
.space
.outputs()
.filter(|output| layer_map_for_output(output).layer_geometry(&layer).is_some())
.filter(|output| {
layer_map_for_output(output)
.layer_geometry(&layer)
.is_some()
})
.cloned()
.collect::<Vec<_>>();
@@ -154,9 +173,9 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
}
let policy = layer.cached_state();
let current = LayoutState::current(&layer);
let previous = layer
.user_data()
.get_or_insert_threadsafe(|| std::sync::Mutex::new(None::<(LayoutState, KeyboardInteractivity)>));
let previous = layer.user_data().get_or_insert_threadsafe(|| {
std::sync::Mutex::new(None::<(LayoutState, KeyboardInteractivity)>)
});
let (changed, requested_focus) = {
let mut previous = previous.lock().unwrap();
let changed = previous.as_ref().map(|(layout, _)| layout) != Some(&current);
@@ -191,7 +210,10 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
};
state.reconcile_layer_keyboard_focus();
if opening_interaction
&& !state.seat.get_keyboard().is_some_and(|keyboard| keyboard.is_grabbed())
&& !state
.seat
.get_keyboard()
.is_some_and(|keyboard| keyboard.is_grabbed())
&& !state.session_lock.active()
&& !state.input_capture.captures(1)
&& state.layer_keyboard_eligible(&layer)
@@ -199,11 +221,15 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
&& matches!(policy.layer, Layer::Top | Layer::Overlay)
&& !layer_has_keyboard_focus(state, &layer)
{
state.seat.get_keyboard().expect("seat has a keyboard").set_focus(
state,
Some(layer.wl_surface().clone()),
smithay::utils::SERIAL_COUNTER.next_serial(),
);
state
.seat
.get_keyboard()
.expect("seat has a keyboard")
.set_focus(
state,
Some(layer.wl_surface().clone()),
smithay::utils::SERIAL_COUNTER.next_serial(),
);
}
if let Err(error) = state.display_handle.flush_clients() {
@@ -220,9 +246,10 @@ impl Ferese {
/// Use the renderer state updated by on_commit_buffer_handler, as for windows.
fn layer_mapped_and_visible(&self, layer: &DesktopLayerSurface) -> bool {
layer.layer_surface().alive()
&& smithay::backend::renderer::utils::with_renderer_surface_state(layer.wl_surface(), |state| {
state.buffer().is_some()
})
&& smithay::backend::renderer::utils::with_renderer_surface_state(
layer.wl_surface(),
|state| state.buffer().is_some(),
)
.unwrap_or(false)
&& self.space.outputs().any(|output| {
self.layer_visible_on_output(output, layer.layer())
@@ -239,8 +266,9 @@ impl Ferese {
.find_popup(&surface)
.and_then(|popup| smithay::desktop::find_popup_root_surface(&popup).ok())
.unwrap_or(surface);
(smithay::wayland::compositor::get_role(&root) == Some(smithay::wayland::shell::wlr_layer::LAYER_SURFACE_ROLE))
.then_some(root)
(smithay::wayland::compositor::get_role(&root)
== Some(smithay::wayland::shell::wlr_layer::LAYER_SURFACE_ROLE))
.then_some(root)
}
pub(crate) fn keyboard_focused_layer(&self) -> Option<DesktopLayerSurface> {
@@ -255,7 +283,8 @@ impl Ferese {
for priority in [Layer::Overlay, Layer::Top] {
if let Some(layer) = focused.as_ref().filter(|layer| {
layer.layer() == priority
&& layer.cached_state().keyboard_interactivity == KeyboardInteractivity::Exclusive
&& layer.cached_state().keyboard_interactivity
== KeyboardInteractivity::Exclusive
&& self.layer_keyboard_eligible(layer)
}) {
return Some(layer.clone());
@@ -313,7 +342,9 @@ impl Ferese {
}
fn layer_has_keyboard_focus(state: &Ferese, layer: &DesktopLayerSurface) -> bool {
state.keyboard_focused_layer().is_some_and(|focused| focused == *layer)
state
.keyboard_focused_layer()
.is_some_and(|focused| focused == *layer)
}
#[cfg(test)]
@@ -349,7 +380,10 @@ mod tests {
original,
LayoutState::from_policy(policy, Rectangle::from_size((800, 33).into()))
);
assert_ne!(original, LayoutState::from_policy(policy, Rectangle::default()));
assert_ne!(
original,
LayoutState::from_policy(policy, Rectangle::default())
);
}
}
@@ -151,7 +151,13 @@ impl Client {
) -> Vec<Vec<u32>> {
if output == 0 {
// Reserve the output slot with a spare surface for unspecified requests.
request(&mut self.wire, self.compositor.id().protocol_id(), 0, &[5], None);
request(
&mut self.wire,
self.compositor.id().protocol_id(),
0,
&[5],
None,
);
}
request(
&mut self.wire,
@@ -199,8 +205,9 @@ fn assert_closed_without_configure(messages: &[Vec<u32>]) {
#[test]
fn zero_outputs_close_new_layer_roles() {
if !crate::startup_tests::private_runtime("handlers::layer_shell::output_tests::zero_outputs_close_new_layer_roles")
{
if !crate::startup_tests::private_runtime(
"handlers::layer_shell::output_tests::zero_outputs_close_new_layer_roles",
) {
return;
}
let (mut state, mut events) = fixture();
@@ -259,8 +266,9 @@ fn unspecified_output_uses_focused_registered_output() {
#[test]
fn valid_explicit_output_is_preserved() {
if !crate::startup_tests::private_runtime("handlers::layer_shell::output_tests::valid_explicit_output_is_preserved")
{
if !crate::startup_tests::private_runtime(
"handlers::layer_shell::output_tests::valid_explicit_output_is_preserved",
) {
return;
}
let (mut state, mut events) = fixture();
+43 -13
View File
@@ -28,7 +28,9 @@ use smithay::wayland::keyboard_shortcuts_inhibit::{
KeyboardShortcutsInhibitorSeat,
};
use smithay::wayland::output::OutputHandler;
use smithay::wayland::pointer_constraints::{PointerConstraint, PointerConstraintsHandler, with_pointer_constraint};
use smithay::wayland::pointer_constraints::{
PointerConstraint, PointerConstraintsHandler, with_pointer_constraint,
};
use smithay::wayland::selection::SelectionHandler;
use smithay::wayland::selection::data_device::{
DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
@@ -54,12 +56,17 @@ impl SeatHandler for Ferese {
if let CursorImageStatus::Named(icon) = &image
&& !self.named_cursors.contains_key(icon)
{
self.cursor_loader.request(*icon, self.cursor_animation.buffer_scale);
self.cursor_loader
.request(*icon, self.cursor_animation.buffer_scale);
}
self.cursor_animation.reset |= match (&self.cursor_status, &image) {
(CursorImageStatus::Named(previous), CursorImageStatus::Named(next)) => previous != next,
(CursorImageStatus::Named(previous), CursorImageStatus::Named(next)) => {
previous != next
}
(CursorImageStatus::Hidden, CursorImageStatus::Hidden) => false,
(CursorImageStatus::Surface(previous), CursorImageStatus::Surface(next)) => previous != next,
(CursorImageStatus::Surface(previous), CursorImageStatus::Surface(next)) => {
previous != next
}
_ => true,
};
self.cursor_status = image;
@@ -74,7 +81,10 @@ impl SeatHandler for Ferese {
self.pending_layer_focus = None;
// Smithay has already sent enter. last_enter uses a separate mutex;
// querying current_focus here would reenter the held keyboard lock.
if let Some(serial) = seat.get_keyboard().and_then(|keyboard| keyboard.last_enter()) {
if let Some(serial) = seat
.get_keyboard()
.and_then(|keyboard| keyboard.last_enter())
{
self.record_activation_input(serial, focused.cloned());
}
@@ -86,7 +96,9 @@ impl SeatHandler for Ferese {
{
inhibitor.inactivate();
}
if let Some(inhibitor) = focused.and_then(|surface| seat.keyboard_shortcuts_inhibitor_for_surface(surface)) {
if let Some(inhibitor) =
focused.and_then(|surface| seat.keyboard_shortcuts_inhibitor_for_surface(surface))
{
inhibitor.activate();
self.active_shortcuts_inhibitor = Some(inhibitor);
}
@@ -136,7 +148,10 @@ impl KeyboardShortcutsInhibitHandler for Ferese {
}
fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
let focused = self.seat.get_keyboard().and_then(|keyboard| keyboard.current_focus());
let focused = self
.seat
.get_keyboard()
.and_then(|keyboard| keyboard.current_focus());
if focused.as_ref() == Some(inhibitor.wl_surface()) {
inhibitor.activate();
@@ -216,7 +231,9 @@ impl Ferese {
let output = output
.or_else(|| self.focused_output())
.or_else(|| self.space.outputs().next());
with_states(surface, |states| set_surface_preferences(surface, states, output));
with_states(surface, |states| {
set_surface_preferences(surface, states, output)
});
}
}
@@ -226,9 +243,18 @@ fn set_surface_preferences(
output: Option<&smithay::output::Output>,
) {
let scale = output.map_or(1.0, |output| output.current_scale().fractional_scale());
let transform = output.map_or(smithay::utils::Transform::Normal, |output| output.current_transform());
smithay::wayland::compositor::send_surface_state(surface, states, scale.ceil() as i32, transform);
with_fractional_scale(states, |surface_scale| surface_scale.set_preferred_scale(scale));
let transform = output.map_or(smithay::utils::Transform::Normal, |output| {
output.current_transform()
});
smithay::wayland::compositor::send_surface_state(
surface,
states,
scale.ceil() as i32,
transform,
);
with_fractional_scale(states, |surface_scale| {
surface_scale.set_preferred_scale(scale)
});
}
pub(crate) fn set_surface_tree_output(surface: &WlSurface, output: &smithay::output::Output) {
@@ -337,13 +363,17 @@ impl PrimarySelectionHandler for Ferese {
}
impl smithay::wayland::selection::wlr_data_control::DataControlHandler for Ferese {
fn data_control_state(&mut self) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {
fn data_control_state(
&mut self,
) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {
&mut self.wlr_data_control_state
}
}
impl smithay::wayland::selection::ext_data_control::DataControlHandler for Ferese {
fn data_control_state(&mut self) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {
fn data_control_state(
&mut self,
) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {
&mut self.ext_data_control_state
}
}
+37 -14
View File
@@ -11,7 +11,9 @@ use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::{
};
use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
use smithay::reexports::wayland_server::protocol::wl_shm;
use smithay::reexports::wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource};
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use smithay::utils::{Buffer, Logical, Rectangle, Size};
use smithay::wayland::shm::with_buffer_contents_mut;
@@ -157,7 +159,8 @@ impl PendingScreencopy {
pub(crate) fn capture_allowed() -> bool {
static ALLOWED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ALLOWED.get_or_init(|| capture_opted_in(std::env::var_os("FERESE_ENABLE_SCREENCOPY").as_deref()))
*ALLOWED
.get_or_init(|| capture_opted_in(std::env::var_os("FERESE_ENABLE_SCREENCOPY").as_deref()))
}
fn capture_opted_in(value: Option<&std::ffi::OsStr>) -> bool {
@@ -321,7 +324,9 @@ impl Ferese {
let captures = std::mem::take(&mut self.pending_screencopies);
for capture in captures {
if &capture.output != output || !cursor_overlay_matches(capture.overlay_cursor, overlay_cursor) {
if &capture.output != output
|| !cursor_overlay_matches(capture.overlay_cursor, overlay_cursor)
{
remaining.push(capture);
continue;
}
@@ -332,7 +337,8 @@ impl Ferese {
}
framebuffer_binding_changed = true;
if complete_capture(renderer, framebuffer, &capture, self.start_time.elapsed()).is_err() {
if complete_capture(renderer, framebuffer, &capture, self.start_time.elapsed()).is_err()
{
capture.fail();
}
}
@@ -342,15 +348,18 @@ impl Ferese {
}
pub(crate) fn has_pending_screencopy(&self, output: &Output, overlay_cursor: bool) -> bool {
self.pending_screencopies
.iter()
.any(|capture| capture.output == *output && cursor_overlay_matches(capture.overlay_cursor, overlay_cursor))
self.pending_screencopies.iter().any(|capture| {
capture.output == *output
&& cursor_overlay_matches(capture.overlay_cursor, overlay_cursor)
})
}
pub(crate) fn fail_screencopies(&mut self, output: &Output, overlay_cursor: bool) {
let mut remaining = Vec::new();
for capture in std::mem::take(&mut self.pending_screencopies) {
if capture.output == *output && cursor_overlay_matches(capture.overlay_cursor, overlay_cursor) {
if capture.output == *output
&& cursor_overlay_matches(capture.overlay_cursor, overlay_cursor)
{
capture.sink.fail();
} else {
remaining.push(capture);
@@ -422,7 +431,10 @@ fn create_frame(
}
}
fn capture_region(output: &Output, requested: Option<Rectangle<i32, Logical>>) -> Option<Rectangle<i32, Buffer>> {
fn capture_region(
output: &Output,
requested: Option<Rectangle<i32, Logical>>,
) -> Option<Rectangle<i32, Buffer>> {
let mode = output.current_mode()?;
let scale = output.current_scale().fractional_scale();
let transform = output.current_transform();
@@ -458,7 +470,9 @@ pub(crate) fn capture_region_for_geometry(
}
let logical_size = output_logical_size(mode_size, scale, transform);
let output_region = Rectangle::from_size(logical_size);
let logical_region = requested.unwrap_or(output_region).intersection(output_region)?;
let logical_region = requested
.unwrap_or(output_region)
.intersection(output_region)?;
if logical_region.size.w <= 0 || logical_region.size.h <= 0 {
return None;
@@ -509,7 +523,9 @@ where
tracing::warn!(?error, "failed to map output framebuffer copy");
})?;
let expected = capture.region.size;
let stride = (expected.w as usize).checked_mul(BYTES_PER_PIXEL).ok_or(())?;
let stride = (expected.w as usize)
.checked_mul(BYTES_PER_PIXEL)
.ok_or(())?;
let bytes = stride.checked_mul(expected.h as usize).ok_or(())?;
if source.len() < bytes {
return Err(());
@@ -567,7 +583,11 @@ where
frame.flags(zwlr_screencopy_frame_v1::Flags::empty());
let seconds = timestamp.as_secs();
frame.ready((seconds >> 32) as u32, seconds as u32, timestamp.subsec_nanos());
frame.ready(
(seconds >> 32) as u32,
seconds as u32,
timestamp.subsec_nanos(),
);
}
Ok(())
}
@@ -596,7 +616,8 @@ mod tests {
let mode = Size::<i32, Physical>::from((1_920, 1_080));
let requested = Rectangle::new((-10, 20).into(), (110, 50).into());
let region = capture_region_for_geometry(mode, 2.0, Transform::Normal, Some(requested)).unwrap();
let region =
capture_region_for_geometry(mode, 2.0, Transform::Normal, Some(requested)).unwrap();
assert_eq!(region, Rectangle::new((0, 40).into(), (200, 100).into()));
}
@@ -606,7 +627,9 @@ mod tests {
let mode = Size::<i32, Physical>::from((1_920, 1_080));
let requested = Rectangle::new((1_000, 600).into(), (100, 100).into());
assert!(capture_region_for_geometry(mode, 2.0, Transform::Normal, Some(requested),).is_none());
assert!(
capture_region_for_geometry(mode, 2.0, Transform::Normal, Some(requested),).is_none()
);
}
#[test]
+341 -81
View File
@@ -40,7 +40,9 @@ const MAX_CANVAS_BYTES: usize = 128 * 1024 * 1024;
impl Canvas {
fn new(width: u32, height: u32) -> Option<Self> {
let length = (width as usize).checked_mul(height as usize)?.checked_mul(4)?;
let length = (width as usize)
.checked_mul(height as usize)?
.checked_mul(4)?;
if width == 0 || height == 0 || length > MAX_CANVAS_BYTES {
return None;
}
@@ -83,7 +85,11 @@ impl Canvas {
}
}
fn scale_and_convert(frame: &OutputFrame, target_width: usize, target_height: usize) -> Option<Vec<u8>> {
fn scale_and_convert(
frame: &OutputFrame,
target_width: usize,
target_height: usize,
) -> Option<Vec<u8>> {
let (upright_width, upright_height) = upright_size(frame);
if upright_width <= 0 || upright_height <= 0 || target_width == 0 || target_height == 0 {
return None;
@@ -95,8 +101,13 @@ fn scale_and_convert(frame: &OutputFrame, target_width: usize, target_height: us
if upright_width as usize == target_width && upright_height as usize == target_height {
for row in 0..target_height {
for column in 0..target_width {
let (x, y) =
upright_source_pixel(column as i32, row as i32, frame.width, frame.height, frame.transform);
let (x, y) = upright_source_pixel(
column as i32,
row as i32,
frame.width,
frame.height,
frame.transform,
);
let from = (y as usize)
.checked_mul(frame.stride)?
.checked_add(x as usize * BYTES_PER_PIXEL)?;
@@ -147,7 +158,13 @@ fn scale_and_convert(frame: &OutputFrame, target_width: usize, target_height: us
Some(out)
}
fn upright_source_pixel(x: i32, y: i32, width: i32, height: i32, transform: Transform) -> (i32, i32) {
fn upright_source_pixel(
x: i32,
y: i32,
width: i32,
height: i32,
transform: Transform,
) -> (i32, i32) {
match transform {
Transform::_90 => (y, height - 1 - x),
Transform::_180 => (width - 1 - x, height - 1 - y),
@@ -173,7 +190,10 @@ pub(crate) fn compose(frames: &[OutputFrame]) -> Result<Canvas, String> {
}
let (left, top) = logical_origin(frames).ok_or("Screenshot region is empty")?;
let scale = frames.iter().map(|frame| frame.scale).fold(0.0_f64, f64::max);
let scale = frames
.iter()
.map(|frame| frame.scale)
.fold(0.0_f64, f64::max);
if !(scale.is_finite() && scale > 0.0) {
return Err("Screenshot outputs have an invalid scale".into());
}
@@ -200,15 +220,22 @@ pub(crate) fn compose(frames: &[OutputFrame]) -> Result<Canvas, String> {
if width <= 0 || height <= 0 {
return Err("Screenshot region is empty".into());
}
let mut canvas = Canvas::new(width as u32, height as u32).ok_or("Screenshot image is too large to encode")?;
let mut canvas = Canvas::new(width as u32, height as u32)
.ok_or("Screenshot image is too large to encode")?;
for (frame, (x0, y0, target_width, target_height)) in frames.iter().zip(placement) {
if target_width == 0 || target_height == 0 {
continue;
}
let scaled =
scale_and_convert(frame, target_width as usize, target_height as usize).ok_or("Invalid capture size")?;
canvas.blit(&scaled, target_width as usize, target_height as usize, x0, y0);
let scaled = scale_and_convert(frame, target_width as usize, target_height as usize)
.ok_or("Invalid capture size")?;
canvas.blit(
&scaled,
target_width as usize,
target_height as usize,
x0,
y0,
);
}
Ok(canvas)
@@ -269,7 +296,12 @@ fn logical_origin(frames: &[OutputFrame]) -> Option<(i32, i32)> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Geometry {
All,
Region { x: i32, y: i32, width: i32, height: i32 },
Region {
x: i32,
y: i32,
width: i32,
height: i32,
},
}
impl Geometry {
@@ -279,9 +311,16 @@ impl Geometry {
if width <= 0 || height <= 0 {
return Err("Screenshot region must have a positive size".into());
}
x.checked_add(width).ok_or("screenshot geometry overflows")?;
y.checked_add(height).ok_or("screenshot geometry overflows")?;
Ok(Geometry::Region { x, y, width, height })
x.checked_add(width)
.ok_or("screenshot geometry overflows")?;
y.checked_add(height)
.ok_or("screenshot geometry overflows")?;
Ok(Geometry::Region {
x,
y,
width,
height,
})
}
}
@@ -402,7 +441,8 @@ impl Budget {
fn reserve(self: &Arc<Self>, bytes: usize) -> Result<BudgetPermit, String> {
self.used
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |used| {
used.checked_add(bytes).filter(|total| *total <= MAX_PENDING_BYTES)
used.checked_add(bytes)
.filter(|total| *total <= MAX_PENDING_BYTES)
})
.map_err(|_| "Screenshot memory budget is exhausted".to_string())?;
Ok(BudgetPermit {
@@ -492,7 +532,10 @@ impl Coordinator {
response_id,
reply,
state: RequestState::Collecting,
parts: specs.into_iter().map(|spec| (spec, PartState::Pending)).collect(),
parts: specs
.into_iter()
.map(|spec| (spec, PartState::Pending))
.collect(),
received: 0,
deadline: now + REQUEST_TIMEOUT,
permit: Some(permit),
@@ -503,7 +546,9 @@ impl Coordinator {
}
pub(crate) fn permit(&self, request: u64) -> Option<BudgetPermit> {
self.requests.get(&request).and_then(|entry| entry.permit.clone())
self.requests
.get(&request)
.and_then(|entry| entry.permit.clone())
}
// Used by the completion tests to assert that a request is still tracked.
@@ -519,7 +564,12 @@ impl Coordinator {
)
}
pub(crate) fn on_part(&mut self, request: u64, part: usize, result: Result<CaptureBuffer, String>) -> Action {
pub(crate) fn on_part(
&mut self,
request: u64,
part: usize,
result: Result<CaptureBuffer, String>,
) -> Action {
let Some(entry) = self.requests.get_mut(&request) else {
return Action::None;
};
@@ -572,7 +622,10 @@ impl Coordinator {
Action::Encode {
request,
frames,
permit: entry.permit.take().expect("collecting request owns its reservation"),
permit: entry
.permit
.take()
.expect("collecting request owns its reservation"),
}
}
@@ -613,7 +666,10 @@ impl Coordinator {
.map(|(id, _)| *id)
.collect();
for id in &expired {
self.reject_inner(*id, "Screenshot timed out: an output was not redrawn in time");
self.reject_inner(
*id,
"Screenshot timed out: an output was not redrawn in time",
);
}
expired
}
@@ -728,7 +784,10 @@ impl PlannedPart {
// Turns a requested logical region into the per-output reads that cover it.
// The logical-to-buffer conversion is shared with the screencopy path so both
// agree on rounding at fractional scales.
pub(crate) fn plan(geometry: &Geometry, outputs: &[OutputLayout]) -> Result<Vec<PlannedPart>, String> {
pub(crate) fn plan(
geometry: &Geometry,
outputs: &[OutputLayout],
) -> Result<Vec<PlannedPart>, String> {
if outputs.is_empty() {
return Err("No outputs are enabled".into());
}
@@ -739,7 +798,8 @@ pub(crate) fn plan(geometry: &Geometry, outputs: &[OutputLayout]) -> Result<Vec<
{
return Err("Screenshot region must have a positive size".into());
}
let logical_sizes: Vec<Size<i32, Logical>> = outputs.iter().map(OutputLayout::logical_size).collect();
let logical_sizes: Vec<Size<i32, Logical>> =
outputs.iter().map(OutputLayout::logical_size).collect();
let requested = match geometry {
Geometry::All => {
@@ -751,8 +811,10 @@ pub(crate) fn plan(geometry: &Geometry, outputs: &[OutputLayout]) -> Result<Vec<
};
union = Some(match union {
Some(current) => {
let top_left =
Point::<i32, Logical>::new(current.loc.x.min(rect.loc.x), current.loc.y.min(rect.loc.y));
let top_left = Point::<i32, Logical>::new(
current.loc.x.min(rect.loc.x),
current.loc.y.min(rect.loc.y),
);
let bottom_right = Point::<i32, Logical>::new(
(current.loc.x + current.size.w).max(rect.loc.x + rect.size.w),
(current.loc.y + current.size.h).max(rect.loc.y + rect.size.h),
@@ -764,7 +826,12 @@ pub(crate) fn plan(geometry: &Geometry, outputs: &[OutputLayout]) -> Result<Vec<
}
union.expect("outputs is not empty")
}
Geometry::Region { x, y, width, height } => Rectangle {
Geometry::Region {
x,
y,
width,
height,
} => Rectangle {
loc: (*x, *y).into(),
size: (*width, *height).into(),
},
@@ -792,7 +859,11 @@ pub(crate) fn plan(geometry: &Geometry, outputs: &[OutputLayout]) -> Result<Vec<
}
// capture_region_for_geometry works in output-local logical space.
let local = Rectangle {
loc: (clipped.loc.x - output.location.x, clipped.loc.y - output.location.y).into(),
loc: (
clipped.loc.x - output.location.x,
clipped.loc.y - output.location.y,
)
.into(),
size: clipped.size,
};
// The clip above already intersected against this same logical rect, so
@@ -839,19 +910,34 @@ mod planning {
}
fn region(x: i32, y: i32, width: i32, height: i32) -> Geometry {
Geometry::Region { x, y, width, height }
Geometry::Region {
x,
y,
width,
height,
}
}
#[test]
fn a_crop_crossing_a_seam_splits_into_one_part_per_output() {
let outputs = [output(0, 0, 1920, 1080, 1.0), output(1920, 0, 1920, 1080, 1.0)];
let outputs = [
output(0, 0, 1920, 1080, 1.0),
output(1920, 0, 1920, 1080, 1.0),
];
let parts = plan(&region(1800, 0, 200, 100), &outputs).unwrap();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].index, 0);
assert_eq!((parts[0].logical.loc.x, parts[0].logical.size.w), (1800, 120));
assert_eq!(
(parts[1].index, parts[1].logical.loc.x, parts[1].logical.size.w),
(parts[0].logical.loc.x, parts[0].logical.size.w),
(1800, 120)
);
assert_eq!(
(
parts[1].index,
parts[1].logical.loc.x,
parts[1].logical.size.w
),
(1, 1920, 80)
);
// The clipped widths must add back up to the request.
@@ -884,8 +970,14 @@ mod planning {
let outputs = [output(0, 0, 800, 600, 1.0), output(800, 0, 1600, 1200, 1.5)];
let parts = plan(&region(600, 0, 400, 300), &outputs).unwrap();
assert_eq!(parts.len(), 2);
assert_eq!((parts[0].logical.loc.x, parts[0].logical.size.w), (600, 200));
assert_eq!((parts[1].logical.loc.x, parts[1].logical.size.w), (800, 200));
assert_eq!(
(parts[0].logical.loc.x, parts[0].logical.size.w),
(600, 200)
);
assert_eq!(
(parts[1].logical.loc.x, parts[1].logical.size.w),
(800, 200)
);
// The 1.5 output reads back at 1.5x while still reporting logical 200.
let spec = parts[1].spec(&outputs[1]);
assert_eq!(spec.logical_width, 200);
@@ -895,7 +987,10 @@ mod planning {
#[test]
fn all_geometry_unions_every_output() {
let outputs = [output(0, 0, 1920, 1080, 1.0), output(-1280, -400, 1280, 800, 1.0)];
let outputs = [
output(0, 0, 1920, 1080, 1.0),
output(-1280, -400, 1280, 800, 1.0),
];
let parts = plan(&Geometry::All, &outputs).unwrap();
assert_eq!(parts.len(), 2);
// Neither output is moved into a normalised origin at plan time.
@@ -906,7 +1001,10 @@ mod planning {
#[test]
fn a_crop_misses_outputs_that_do_not_intersect() {
let outputs = [output(0, 0, 1920, 1080, 1.0), output(1920, 0, 1920, 1080, 1.0)];
let outputs = [
output(0, 0, 1920, 1080, 1.0),
output(1920, 0, 1920, 1080, 1.0),
];
let parts = plan(&region(4000, 0, 100, 100), &outputs);
assert!(parts.is_err(), "a region off the desktop is reported");
}
@@ -916,11 +1014,17 @@ mod planning {
// The second output starts exactly where the region ends. It contributes
// nothing, so the capture must still succeed with just the first output
// rather than failing on a region it does not overlap.
let outputs = [output(0, 0, 1920, 1080, 1.0), output(1920, 0, 1920, 1080, 1.0)];
let outputs = [
output(0, 0, 1920, 1080, 1.0),
output(1920, 0, 1920, 1080, 1.0),
];
let parts = plan(&region(100, 100, 1820, 980), &outputs).expect("captures the overlap");
assert_eq!(parts.len(), 1, "only the overlapping output is read");
assert_eq!(parts[0].index, 0);
assert_eq!((parts[0].logical.size.w, parts[0].logical.size.h), (1820, 980));
assert_eq!(
(parts[0].logical.size.w, parts[0].logical.size.h),
(1820, 980)
);
}
#[test]
@@ -930,10 +1034,16 @@ mod planning {
// A 90-degree transform turns the 1920x1080 mode into a 1080x1920
// logical area, and the readback follows the same rotation.
let parts = plan(&region(0, 0, 1080, 400), std::slice::from_ref(&rotated)).unwrap();
assert_eq!((parts[0].logical.size.w, parts[0].logical.size.h), (1080, 400));
assert_eq!(
(parts[0].logical.size.w, parts[0].logical.size.h),
(1080, 400)
);
let spec = parts[0].spec(&rotated);
assert_eq!(spec.transform, Transform::_90);
assert!(spec.buffer_height > spec.buffer_width, "buffer axes are swapped");
assert!(
spec.buffer_height > spec.buffer_width,
"buffer axes are swapped"
);
}
#[test]
@@ -1013,9 +1123,17 @@ mod completion {
let (reply, received) = sync_channel(8);
let mut coordinator = Coordinator::new();
coordinator
.admit(1, 42, reply.clone(), (0..parts).map(|_| spec(2, 2)).collect())
.admit(
1,
42,
reply.clone(),
(0..parts).map(|_| spec(2, 2)).collect(),
)
.expect("admitted");
Self { coordinator, received }
Self {
coordinator,
received,
}
}
fn response(&self) -> Option<Response> {
@@ -1036,7 +1154,11 @@ mod completion {
};
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pixels.as_ptr(), allocation, "readback pixels were copied");
assert_eq!(
frames[0].pixels.as_ptr(),
allocation,
"readback pixels were copied"
);
assert_eq!(frames[0].pixels, vec![137; 16]);
}
@@ -1049,13 +1171,21 @@ mod completion {
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2))),
Action::None
));
assert!(harness.coordinator.is_live(id), "still collecting after part 0");
assert!(
harness.coordinator.is_live(id),
"still collecting after part 0"
);
assert!(matches!(
harness.coordinator.on_part(id, 1, Err("readback failed".into())),
harness
.coordinator
.on_part(id, 1, Err("readback failed".into())),
Action::None
));
assert!(!harness.coordinator.is_live(id), "one failed part ends the request");
assert!(
!harness.coordinator.is_live(id),
"one failed part ends the request"
);
let response = harness.response().expect("caller is always answered");
assert_eq!(response.id, 42);
@@ -1070,7 +1200,9 @@ mod completion {
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2)));
// Output 1 is removed from the layout, so its pending capture is failed.
harness.coordinator.on_part(id, 1, Err("output was removed".into()));
harness
.coordinator
.on_part(id, 1, Err("output was removed".into()));
assert!(!harness.coordinator.is_live(id));
assert!(harness.response().unwrap().error.is_some());
@@ -1109,14 +1241,26 @@ mod completion {
fn admission_enforces_one_budget_across_requests_and_late_readbacks() {
let (reply, _received) = sync_channel(8);
let mut coordinator = Coordinator::new();
let first = coordinator.admit(1, 1, reply.clone(), vec![spec(4096, 8192)]).unwrap();
let second = coordinator.admit(1, 2, reply.clone(), vec![spec(4096, 8192)]).unwrap();
assert!(coordinator.admit(1, 3, reply.clone(), vec![spec(2, 2)]).is_err());
let first = coordinator
.admit(1, 1, reply.clone(), vec![spec(4096, 8192)])
.unwrap();
let second = coordinator
.admit(1, 2, reply.clone(), vec![spec(4096, 8192)])
.unwrap();
assert!(
coordinator
.admit(1, 3, reply.clone(), vec![spec(2, 2)])
.is_err()
);
// A cancelled request can still have a readback retained by a sink or channel.
let late_readback = coordinator.permit(first).unwrap();
coordinator.reject(first, "cancelled");
assert!(coordinator.admit(1, 4, reply.clone(), vec![spec(2, 2)]).is_err());
assert!(
coordinator
.admit(1, 4, reply.clone(), vec![spec(2, 2)])
.is_err()
);
drop(late_readback);
assert!(coordinator.admit(1, 5, reply, vec![spec(2, 2)]).is_ok());
coordinator.reject(second, "finished");
@@ -1127,19 +1271,48 @@ mod completion {
let mut harness = Harness::new(1);
let id = harness.coordinator.next_id;
let bytes = harness.coordinator.budget.used.load(Ordering::Acquire);
let Action::Encode { frames, permit, .. } = harness.coordinator.on_part(id, 0, Ok(buffer(2, 2))) else {
let Action::Encode { frames, permit, .. } =
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2)))
else {
panic!("ready request should encode");
};
harness.coordinator.reject(id, "cancelled while encoder owns pixels");
assert_eq!(harness.coordinator.budget.used.load(Ordering::Acquire), bytes);
assert!(harness.coordinator.budget.reserve(MAX_PENDING_BYTES).is_err());
harness
.coordinator
.reject(id, "cancelled while encoder owns pixels");
assert_eq!(
harness.coordinator.budget.used.load(Ordering::Acquire),
bytes
);
assert!(
harness
.coordinator
.budget
.reserve(MAX_PENDING_BYTES)
.is_err()
);
drop(frames);
drop(permit);
assert_eq!(harness.coordinator.budget.used.load(Ordering::Acquire), 0);
let first = harness.coordinator.budget.reserve(MAX_PENDING_BYTES / 2 + 1).unwrap();
assert!(harness.coordinator.budget.reserve(MAX_PENDING_BYTES / 2).is_err());
let first = harness
.coordinator
.budget
.reserve(MAX_PENDING_BYTES / 2 + 1)
.unwrap();
assert!(
harness
.coordinator
.budget
.reserve(MAX_PENDING_BYTES / 2)
.is_err()
);
drop(first);
assert!(harness.coordinator.budget.reserve(MAX_PENDING_BYTES).is_ok());
assert!(
harness
.coordinator
.budget
.reserve(MAX_PENDING_BYTES)
.is_ok()
);
}
#[test]
@@ -1152,7 +1325,10 @@ mod completion {
Action::None
));
let action = harness.coordinator.on_part(id, 1, Ok(buffer(2, 2)));
let Action::Encode { request, frames, .. } = action else {
let Action::Encode {
request, frames, ..
} = action
else {
panic!("expected the request to move to encoding");
};
assert_eq!(request, id);
@@ -1177,7 +1353,9 @@ mod completion {
// Unlocking does not resurrect the request; the file the worker
// eventually produces is ours to delete.
assert!(!harness.coordinator.is_live(id));
let action = harness.coordinator.on_encoded(id, Ok(PathBuf::from("/tmp/late.png")));
let action = harness
.coordinator
.on_encoded(id, Ok(PathBuf::from("/tmp/late.png")));
assert!(matches!(action, Action::DiscardFile(ref path) if path.ends_with("late.png")));
assert!(harness.response().is_none(), "no late success is delivered");
}
@@ -1189,7 +1367,9 @@ mod completion {
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2)));
assert!(harness.coordinator.is_encoding(id));
assert_eq!(
harness.coordinator.terminate_all_with_reason("Capture privacy changed"),
harness
.coordinator
.terminate_all_with_reason("Capture privacy changed"),
vec![id]
);
assert!(harness.response().unwrap().error.is_some());
@@ -1208,7 +1388,9 @@ mod completion {
let id = harness.coordinator.next_id;
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2)));
harness.coordinator.on_encoded(id, Ok(PathBuf::from("/tmp/shot.png")));
harness
.coordinator
.on_encoded(id, Ok(PathBuf::from("/tmp/shot.png")));
let response = harness.response().expect("success is answered");
assert_eq!(response.id, 42);
assert!(response.error.is_none());
@@ -1216,7 +1398,9 @@ mod completion {
// A duplicate completion for the same request has nowhere to go, and
// its file is an orphan rather than a second delivery.
let action = harness.coordinator.on_encoded(id, Ok(PathBuf::from("/tmp/again.png")));
let action = harness
.coordinator
.on_encoded(id, Ok(PathBuf::from("/tmp/again.png")));
assert!(matches!(action, Action::DiscardFile(_)));
// A delivered path is reported separately so the caller cannot
@@ -1239,7 +1423,9 @@ mod completion {
let id = harness.coordinator.next_id;
harness.coordinator.on_part(id, 0, Ok(buffer(2, 2)));
harness.coordinator.on_encoded(id, Err("encoding failed".into()));
harness
.coordinator
.on_encoded(id, Err("encoding failed".into()));
let response = harness.response().expect("failure is answered");
assert_eq!(response.error.unwrap().message, "encoding failed");
assert!(!harness.coordinator.is_live(id));
@@ -1252,13 +1438,23 @@ mod completion {
// client would never be answered.
let mut harness = Harness::new(2);
let id = harness.coordinator.next_id;
harness.coordinator.requests.get_mut(&id).expect("tracked").deadline = Instant::now() - Duration::from_secs(1);
harness
.coordinator
.requests
.get_mut(&id)
.expect("tracked")
.deadline = Instant::now() - Duration::from_secs(1);
assert_eq!(harness.coordinator.expire_at(Instant::now()), vec![id]);
let response = harness.response().expect("the caller is answered");
assert_eq!(response.error.as_ref().unwrap().code, "screenshot_failed");
assert!(
response.error.as_ref().unwrap().message.contains("timed out"),
response
.error
.as_ref()
.unwrap()
.message
.contains("timed out"),
"the failure explains itself: {:?}",
response.error.as_ref().unwrap().message
);
@@ -1275,7 +1471,10 @@ mod completion {
fn a_live_request_is_not_expired_early() {
let mut harness = Harness::new(1);
let id = harness.coordinator.next_id;
assert!(harness.coordinator.expire_at(Instant::now()).is_empty(), "not yet due");
assert!(
harness.coordinator.expire_at(Instant::now()).is_empty(),
"not yet due"
);
assert!(harness.coordinator.is_live(id));
assert!(harness.response().is_none(), "still unanswered, not failed");
let remaining = harness
@@ -1334,7 +1533,9 @@ mod completion {
);
}
fn observe_deadlines(coordinator: &mut Coordinator) -> std::rc::Rc<std::cell::RefCell<Vec<Option<Instant>>>> {
fn observe_deadlines(
coordinator: &mut Coordinator,
) -> std::rc::Rc<std::cell::RefCell<Vec<Option<Instant>>>> {
let changes = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
let observed = changes.clone();
coordinator.set_deadline_observer(move |deadline| observed.borrow_mut().push(deadline));
@@ -1356,15 +1557,29 @@ mod completion {
.admit_at(1, 42, reply.clone(), vec![spec(2, 2)], late)
.unwrap();
let second = coordinator
.admit_at(2, 43, reply.clone(), vec![spec(2, 2)], late + Duration::from_secs(5))
.admit_at(
2,
43,
reply.clone(),
vec![spec(2, 2)],
late + Duration::from_secs(5),
)
.unwrap();
assert_eq!(
changes.borrow().len(),
2,
"a later request does not rearm the earliest timer"
);
assert!(coordinator.admit_at(1, 44, reply, Vec::new(), late).is_err());
assert_eq!(changes.borrow().len(), 2, "rejected admission does not touch the timer");
assert!(
coordinator
.admit_at(1, 44, reply, Vec::new(), late)
.is_err()
);
assert_eq!(
changes.borrow().len(),
2,
"rejected admission does not touch the timer"
);
assert_eq!(coordinator.terminate_owner(1), vec![first]);
assert!(coordinator.is_live(second));
assert_eq!(coordinator.terminate_owner(99), Vec::<u64>::new());
@@ -1382,7 +1597,10 @@ mod completion {
#[test]
fn deadline_survives_readback_and_encoding_then_disarms_on_completion() {
for result in [Ok(PathBuf::from("/tmp/shot.png")), Err("encoder failed".into())] {
for result in [
Ok(PathBuf::from("/tmp/shot.png")),
Err("encoder failed".into()),
] {
let mut harness = Harness::new(2);
let changes = observe_deadlines(&mut harness.coordinator);
let id = harness.coordinator.next_id;
@@ -1392,11 +1610,19 @@ mod completion {
harness.coordinator.on_part(id, 1, Ok(buffer(2, 2))),
Action::Encode { .. }
));
assert_eq!(&*changes.borrow(), &[deadline], "encoding keeps the original deadline");
assert_eq!(
&*changes.borrow(),
&[deadline],
"encoding keeps the original deadline"
);
harness.coordinator.on_encoded(id, result);
assert_eq!(&*changes.borrow(), &[deadline, None]);
harness.coordinator.on_part(id, 0, Err("late readback".into()));
harness.coordinator.on_encoded(id, Err("late encode".into()));
harness
.coordinator
.on_part(id, 0, Err("late readback".into()));
harness
.coordinator
.on_encoded(id, Err("late encode".into()));
assert_eq!(
changes.borrow().len(),
2,
@@ -1440,7 +1666,11 @@ mod completion {
}
_ => unreachable!(),
}
assert_eq!(changes.borrow().last(), Some(&None), "{cancellation} must disarm");
assert_eq!(
changes.borrow().last(),
Some(&None),
"{cancellation} must disarm"
);
assert_eq!(changes.borrow().len(), 2);
assert!(harness.response().unwrap().error.is_some());
assert!(!harness.coordinator.is_live(id));
@@ -1458,7 +1688,13 @@ mod completion {
.admit_at(1, 42, reply.clone(), vec![spec(2, 2)], now)
.unwrap();
let second = coordinator
.admit_at(2, 43, reply.clone(), vec![spec(2, 2)], now + Duration::from_secs(5))
.admit_at(
2,
43,
reply.clone(),
vec![spec(2, 2)],
now + Duration::from_secs(5),
)
.unwrap();
coordinator.on_part(first, 0, Ok(buffer(2, 2)));
assert!(
@@ -1487,7 +1723,13 @@ mod completion {
Action::DiscardFile(_)
));
coordinator
.admit_at(3, 44, reply, vec![spec(2, 2)], now + Duration::from_secs(3600))
.admit_at(
3,
44,
reply,
vec![spec(2, 2)],
now + Duration::from_secs(3600),
)
.unwrap();
assert_eq!(
changes.borrow().last(),
@@ -1505,7 +1747,10 @@ mod completion {
assert!(!sink.publish(buffer(64, 64)));
let PartOutcome {
request, part, result, ..
request,
part,
result,
..
} = receiver.try_recv().expect("published once");
assert_eq!((request, part), (7, 1));
assert_eq!(result.unwrap().width, 2);
@@ -1522,7 +1767,10 @@ mod completion {
assert!(!sink.publish(buffer(2, 2)));
let PartOutcome {
request, part, result, ..
request,
part,
result,
..
} = receiver.try_recv().expect("answered");
assert_eq!((request, part), (3, 0));
assert!(result.is_err());
@@ -1913,7 +2161,11 @@ mod tests {
// 9 logical at scale 1.5.
assert_eq!((canvas.width, canvas.height), (14, 5));
for column in 0..canvas.width as usize {
assert_eq!(canvas.pixels[column * 4 + 3], 255, "column {column} has a gap");
assert_eq!(
canvas.pixels[column * 4 + 3],
255,
"column {column} has a gap"
);
}
}
@@ -1945,7 +2197,11 @@ mod tests {
let canvas = compose(&[left, right]).unwrap();
assert_eq!(canvas.width, 5);
assert_eq!(&canvas.pixels[2 * 4..3 * 4], &[0, 0, 0, 0], "gap is transparent");
assert_eq!(
&canvas.pixels[2 * 4..3 * 4],
&[0, 0, 0, 0],
"gap is transparent"
);
assert_eq!(canvas.pixels[3], 255);
assert_eq!(canvas.pixels[3 * 4 + 3], 255);
}
@@ -1974,7 +2230,11 @@ mod tests {
#[test]
fn geometry_tolerates_surrounding_whitespace_from_slurp() {
for raw in ["100,200 800x600\n", " 100,200 800x600 ", "100,200 800x600\r\n"] {
for raw in [
"100,200 800x600\n",
" 100,200 800x600 ",
"100,200 800x600\r\n",
] {
assert_eq!(
parse_geometry(raw).unwrap(),
Geometry::Region {
+29 -10
View File
@@ -113,8 +113,9 @@ fn encode_png(canvas: &Canvas) -> Result<Vec<u8>, String> {
if width == 0 || height == 0 {
return Err("Screenshot is empty".into());
}
let image = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(width, height, canvas.pixels.as_slice())
.ok_or("Screenshot buffer does not match its dimensions")?;
let image =
image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(width, height, canvas.pixels.as_slice())
.ok_or("Screenshot buffer does not match its dimensions")?;
let mut out = Vec::new();
let encoder = image::codecs::png::PngEncoder::new_with_quality(
@@ -122,8 +123,14 @@ fn encode_png(canvas: &Canvas) -> Result<Vec<u8>, String> {
image::codecs::png::CompressionType::Fast,
image::codecs::png::FilterType::Adaptive,
);
image::ImageEncoder::write_image(encoder, image.as_raw(), width, height, image::ExtendedColorType::Rgba8)
.map_err(|error| format!("Could not encode screenshot: {error}"))?;
image::ImageEncoder::write_image(
encoder,
image.as_raw(),
width,
height,
image::ExtendedColorType::Rgba8,
)
.map_err(|error| format!("Could not encode screenshot: {error}"))?;
tracing::debug!(encoded = out.len(), "encoded screenshot");
Ok(out)
}
@@ -178,8 +185,8 @@ fn write_private_file_in(
}
fn private_directory() -> Result<PathBuf, String> {
let runtime =
std::env::var_os("XDG_RUNTIME_DIR").ok_or("XDG_RUNTIME_DIR is not set, so screenshots cannot be staged")?;
let runtime = std::env::var_os("XDG_RUNTIME_DIR")
.ok_or("XDG_RUNTIME_DIR is not set, so screenshots cannot be staged")?;
let root = PathBuf::from(runtime);
let directory = root.join("ferese-screenshots");
match fs::symlink_metadata(&directory) {
@@ -194,7 +201,9 @@ fn private_directory() -> Result<PathBuf, String> {
}
Err(error) if error.kind() == io::ErrorKind::NotFound => {
fs::create_dir(&directory)
.and_then(|()| fs::set_permissions(&directory, fs::Permissions::from_mode(DIR_MODE)))
.and_then(|()| {
fs::set_permissions(&directory, fs::Permissions::from_mode(DIR_MODE))
})
.map_err(|error| format!("Could not create screenshot directory: {error}"))?;
}
Err(error) => return Err(format!("Could not inspect screenshot directory: {error}")),
@@ -248,7 +257,11 @@ mod tests {
.expect("decodes back")
.to_rgba8();
assert_eq!(decoded.dimensions(), (4, 3));
assert_eq!(decoded.as_raw(), &canvas.pixels, "encoding changed the pixel data");
assert_eq!(
decoded.as_raw(),
&canvas.pixels,
"encoding changed the pixel data"
);
// The opaque alpha we force is preserved through the round trip.
assert_eq!(decoded.get_pixel(0, 0)[3], 255);
}
@@ -279,7 +292,10 @@ mod tests {
let path = write_private_file(&png).expect("staged");
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, FILE_MODE, "staged screenshots are not group/world readable");
assert_eq!(
mode, FILE_MODE,
"staged screenshots are not group/world readable"
);
fs::remove_file(&path).unwrap();
}
@@ -293,7 +309,10 @@ mod tests {
fs::write(&other, b"not a screenshot").unwrap();
sweep_stale_files();
assert!(fresh.exists(), "a freshly staged screenshot is still in use");
assert!(
fresh.exists(),
"a freshly staged screenshot is still in use"
);
assert!(other.exists(), "unrelated files are left alone");
fs::remove_file(&fresh).unwrap();
+15 -10
View File
@@ -1,7 +1,9 @@
use std::sync::Arc;
use smithay::reexports::wayland_server::Client;
use smithay::wayland::security_context::{SecurityContext, SecurityContextHandler, SecurityContextListenerSource};
use smithay::wayland::security_context::{
SecurityContext, SecurityContextHandler, SecurityContextListenerSource,
};
use crate::Ferese;
use crate::state::ClientState;
@@ -14,15 +16,18 @@ pub(crate) fn unrestricted_client(client: &Client) -> bool {
impl SecurityContextHandler for Ferese {
fn context_created(&mut self, source: SecurityContextListenerSource, context: SecurityContext) {
if let Err(error) = self.loop_handle.insert_source(source, move |stream, _, state| {
let data = ClientState {
security_context: Some(context.clone()),
..Default::default()
};
if let Err(error) = state.display_handle.insert_client(stream, Arc::new(data)) {
tracing::warn!(%error, "failed to register sandboxed Wayland client");
}
}) {
if let Err(error) = self
.loop_handle
.insert_source(source, move |stream, _, state| {
let data = ClientState {
security_context: Some(context.clone()),
..Default::default()
};
if let Err(error) = state.display_handle.insert_client(stream, Arc::new(data)) {
tracing::warn!(%error, "failed to register sandboxed Wayland client");
}
})
{
tracing::warn!(%error, "failed to register security context listener");
}
}
+20 -5
View File
@@ -28,19 +28,28 @@ fn icon_cleanup_messages(destroy_icon: bool) -> Vec<(u32, u16, Vec<u32>)> {
.create_resource::<WlCompositor, GlobalData, Ferese>(&state.display_handle, 6, GlobalData)
.unwrap();
let shell = client
.create_resource::<XdgWmBase, XdgWmBaseUserData, Ferese>(&state.display_handle, 6, Default::default())
.create_resource::<XdgWmBase, XdgWmBaseUserData, Ferese>(
&state.display_handle,
6,
Default::default(),
)
.unwrap();
let shm = client
.create_resource::<WlShm, GlobalData, Ferese>(&state.display_handle, 1, GlobalData)
.unwrap();
let manager = client
.create_resource::<XdgToplevelIconManagerV1, GlobalData, Ferese>(&state.display_handle, 1, GlobalData)
.create_resource::<XdgToplevelIconManagerV1, GlobalData, Ferese>(
&state.display_handle,
1,
GlobalData,
)
.unwrap();
request(&mut wire, compositor.id().protocol_id(), 0, &[2], None);
request(&mut wire, shell.id().protocol_id(), 2, &[3, 2], None);
request(&mut wire, 3, 1, &[4], None);
let mut file = tempfile::tempfile().unwrap();
file.write_all(&0xff112233u32.to_ne_bytes().repeat(32 * 32)).unwrap();
file.write_all(&0xff112233u32.to_ne_bytes().repeat(32 * 32))
.unwrap();
request(
&mut wire,
shm.id().protocol_id(),
@@ -85,9 +94,15 @@ fn destroying_an_assigned_icon_before_its_buffer_keeps_the_client_connected() {
return;
}
let messages = icon_cleanup_messages(true);
assert!(!messages.iter().any(|(object, opcode, _)| *object == 1 && *opcode == 0));
assert!(
messages.iter().any(|(object, opcode, _)| *object == 8 && *opcode == 0),
!messages
.iter()
.any(|(object, opcode, _)| *object == 1 && *opcode == 0)
);
assert!(
messages
.iter()
.any(|(object, opcode, _)| *object == 8 && *opcode == 0),
"valid icon cleanup disconnected the client before its sync reply: {messages:?}"
);
}
+89 -39
View File
@@ -8,7 +8,9 @@ use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_scre
use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1;
use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
use smithay::reexports::wayland_server::protocol::wl_shm;
use smithay::reexports::wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource};
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use smithay::utils::Rectangle;
use smithay::wayland::shm::with_buffer_contents_mut;
@@ -34,7 +36,10 @@ struct Permit {
impl Permit {
fn reserve(budget: &Arc<Mutex<Budget>>, bytes: usize) -> Option<Self> {
let mut available = budget.lock().unwrap();
if bytes == 0 || bytes > MAX_BYTES.saturating_sub(available.bytes) || available.frames >= MAX_FRAMES {
if bytes == 0
|| bytes > MAX_BYTES.saturating_sub(available.bytes)
|| available.frames >= MAX_FRAMES
{
return None;
}
available.bytes += bytes;
@@ -77,7 +82,11 @@ struct Conversion {
pub(crate) fn is_portal(stream: &std::os::unix::net::UnixStream) -> bool {
use std::os::fd::AsRawFd;
let mut credentials = libc::ucred { pid: 0, uid: 0, gid: 0 };
let mut credentials = libc::ucred {
pid: 0,
uid: 0,
gid: 0,
};
let mut length = std::mem::size_of_val(&credentials) as libc::socklen_t;
// SAFETY: the connected socket is live and both out pointers cover their
// initialized stack allocations. SO_PEERCRED identifies the connecting process.
@@ -95,7 +104,10 @@ pub(crate) fn is_portal(stream: &std::os::unix::net::UnixStream) -> bool {
}
let expected = std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|dir| dir.join("xdg-desktop-portal-ferese")))
.and_then(|exe| {
exe.parent()
.map(|dir| dir.join("xdg-desktop-portal-ferese"))
})
.and_then(|exe| std::fs::metadata(exe).ok());
let actual = std::fs::metadata(format!("/proc/{}/exe", credentials.pid)).ok();
matches!((expected, actual), (Some(expected), Some(actual)) if expected.dev() == actual.dev() && expected.ino() == actual.ino())
@@ -178,11 +190,13 @@ fn finish_conversion(state: &Ferese, result: Conversion) {
impl GlobalDispatch<FereseWindowCaptureManagerV1, Global> for Ferese {
fn can_view(client: Client, _: &Global) -> bool {
client.get_data::<crate::state::ClientState>().is_some_and(|state| {
state
.capabilities
.contains(crate::private_client::ClientCapabilities::WINDOW_CAPTURE)
})
client
.get_data::<crate::state::ClientState>()
.is_some_and(|state| {
state
.capabilities
.contains(crate::private_client::ClientCapabilities::WINDOW_CAPTURE)
})
}
fn bind(
@@ -248,7 +262,12 @@ impl Dispatch<FereseWindowCaptureManagerV1, Global> for Ferese {
})
.is_err()
{
*resource.data::<Arc<FrameData>>().unwrap().used.lock().unwrap() = true;
*resource
.data::<Arc<FrameData>>()
.unwrap()
.used
.lock()
.unwrap() = true;
manager.busy(&resource);
resource.failed();
}
@@ -287,7 +306,8 @@ impl Ferese {
.clone();
let geometry = window.geometry();
let scale = output.current_scale().fractional_scale();
let size: smithay::utils::Size<i32, smithay::utils::Physical> = geometry.size.to_physical_precise_round(scale);
let size: smithay::utils::Size<i32, smithay::utils::Physical> =
geometry.size.to_physical_precise_round(scale);
let bytes = usize::try_from(size.w)
.ok()?
.checked_mul(usize::try_from(size.h).ok()?)?
@@ -299,26 +319,29 @@ impl Ferese {
*busy = true;
return None;
};
let cursor = if overlay_cursor && !self.overview.is_presenting() && !self.capture_cursor_protected() {
self.seat.get_pointer().and_then(|pointer| {
let location = pointer.current_location();
if self.window_under_visual(location).as_ref() != Some(&window) {
return None;
}
let (x, y) = self.inverse_presented_window_point(id, location.x, location.y)?;
let surface_point = smithay::utils::Point::from((x, y)) + geometry.loc.to_f64();
let (surface, _) = window.surface_under(surface_point, smithay::desktop::WindowSurfaceType::ALL)?;
if pointer.current_focus().as_ref() != Some(&surface) {
return None;
}
Some(Rectangle::new(
(location - smithay::utils::Point::from((x, y))).to_i32_round(),
geometry.size,
))
})
} else {
None
};
let cursor =
if overlay_cursor && !self.overview.is_presenting() && !self.capture_cursor_protected()
{
self.seat.get_pointer().and_then(|pointer| {
let location = pointer.current_location();
if self.window_under_visual(location).as_ref() != Some(&window) {
return None;
}
let (x, y) = self.inverse_presented_window_point(id, location.x, location.y)?;
let surface_point = smithay::utils::Point::from((x, y)) + geometry.loc.to_f64();
let (surface, _) = window
.surface_under(surface_point, smithay::desktop::WindowSurfaceType::ALL)?;
if pointer.current_focus().as_ref() != Some(&surface) {
return None;
}
Some(Rectangle::new(
(location - smithay::utils::Point::from((x, y))).to_i32_round(),
geometry.size,
))
})
} else {
None
};
let result = if let Some(backend) = self.nested_backend.clone() {
let mut backend = backend.try_borrow_mut().ok()?;
crate::render::capture_window_frame(
@@ -357,10 +380,18 @@ impl Ferese {
}
}
pub(super) fn copy_snapshot(state: &Ferese, resource: &ZwlrScreencopyFrameV1, data: &FrameData, buffer: &WlBuffer) {
pub(super) fn copy_snapshot(
state: &Ferese,
resource: &ZwlrScreencopyFrameV1,
data: &FrameData,
buffer: &WlBuffer,
) {
let mut used = data.used.lock().unwrap();
if *used {
resource.post_error(frame::Error::AlreadyUsed, "window capture frame has already been used");
resource.post_error(
frame::Error::AlreadyUsed,
"window capture frame has already been used",
);
return;
}
*used = true;
@@ -393,7 +424,11 @@ pub(super) fn copy_snapshot(state: &Ferese, resource: &ZwlrScreencopyFrameV1, da
// SAFETY: Smithay owns the writable mapping, validated format/stride and
// checked offset cover the complete snapshot; the source is separately owned.
unsafe {
std::ptr::copy_nonoverlapping(snapshot.pixels.pixels.as_ptr(), destination.add(offset), bytes);
std::ptr::copy_nonoverlapping(
snapshot.pixels.pixels.as_ptr(),
destination.add(offset),
bytes,
);
}
true
})
@@ -459,7 +494,11 @@ pub(crate) mod tests {
.unwrap();
let data = frame_data();
let frame = client
.create_resource::<ZwlrScreencopyFrameV1, Arc<FrameData>, Ferese>(&state.display_handle, 3, data.clone())
.create_resource::<ZwlrScreencopyFrameV1, Arc<FrameData>, Ferese>(
&state.display_handle,
3,
data.clone(),
)
.unwrap();
finish_conversion(
state,
@@ -475,20 +514,27 @@ pub(crate) mod tests {
let data = frame_data();
*data.snapshot.lock().unwrap() = Some(snapshot());
let frame = client
.create_resource::<ZwlrScreencopyFrameV1, Arc<FrameData>, Ferese>(&state.display_handle, 3, data.clone())
.create_resource::<ZwlrScreencopyFrameV1, Arc<FrameData>, Ferese>(
&state.display_handle,
3,
data.clone(),
)
.unwrap();
let buffer = client
.object_from_protocol_id::<WlBuffer>(&state.display_handle, 6)
.unwrap();
let before = with_buffer_contents_mut(&buffer, |ptr, _, layout| {
// SAFETY: the fixture allocates and validates this 64x48 SHM buffer.
unsafe { std::slice::from_raw_parts(ptr.add(layout.offset as usize), 64 * 48 * 4).to_vec() }
unsafe {
std::slice::from_raw_parts(ptr.add(layout.offset as usize), 64 * 48 * 4).to_vec()
}
})
.unwrap();
copy_snapshot(state, &frame, &data, &buffer);
with_buffer_contents_mut(&buffer, |ptr, _, layout| {
// SAFETY: same fixture-owned buffer as above.
let after = unsafe { std::slice::from_raw_parts(ptr.add(layout.offset as usize), 64 * 48 * 4) };
let after =
unsafe { std::slice::from_raw_parts(ptr.add(layout.offset as usize), 64 * 48 * 4) };
if allowed {
assert!(after.iter().all(|byte| *byte == 0));
} else {
@@ -496,7 +542,11 @@ pub(crate) mod tests {
}
// Restore the client's colored buffer for subsequent renderer checks.
unsafe {
std::ptr::copy_nonoverlapping(before.as_ptr(), ptr.add(layout.offset as usize), before.len());
std::ptr::copy_nonoverlapping(
before.as_ptr(),
ptr.add(layout.offset as usize),
before.len(),
);
}
})
.unwrap();
+51 -18
View File
@@ -60,7 +60,9 @@ impl XdgShellHandler for Ferese {
fn parent_changed(&mut self, surface: ToplevelSurface) {
self.refresh_capture_privacy();
self.refresh_idle_inhibition();
let parent = surface.parent().and_then(|parent| self.windows.id_for_surface(&parent));
let parent = surface
.parent()
.and_then(|parent| self.windows.id_for_surface(&parent));
let child = self
.windows
.id_for_surface(surface.wl_surface())
@@ -95,7 +97,12 @@ impl XdgShellHandler for Ferese {
}
}
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
fn reposition_request(
&mut self,
surface: PopupSurface,
positioner: PositionerState,
token: u32,
) {
surface.with_pending_state(|state| {
state.geometry = positioner.get_geometry();
state.positioner = positioner;
@@ -252,7 +259,12 @@ impl XdgShellHandler for Ferese {
};
if let Some(pointer) = seat.get_pointer() {
pointer.set_grab(self, PopupPointerGrab::new(&popup_grab), serial, Focus::Keep);
pointer.set_grab(
self,
PopupPointerGrab::new(&popup_grab),
serial,
Focus::Keep,
);
}
if let Some(keyboard) = seat.get_keyboard() {
keyboard.set_grab(self, PopupKeyboardGrab::new(&popup_grab), serial);
@@ -264,14 +276,21 @@ impl XdgShellHandler for Ferese {
}
}
fn check_grab(seat: &Seat<Ferese>, surface: &WlSurface, serial: Serial) -> Option<GrabStartData<Ferese>> {
fn check_grab(
seat: &Seat<Ferese>,
surface: &WlSurface,
serial: Serial,
) -> Option<GrabStartData<Ferese>> {
let pointer = seat.get_pointer()?;
if !pointer.has_grab(serial) {
return None;
}
let start_data = pointer.grab_start_data()?;
let (focus, _) = start_data.focus.as_ref()?;
focus.id().same_client_as(&surface.id()).then_some(start_data)
focus
.id()
.same_client_as(&surface.id())
.then_some(start_data)
}
impl XdgDecorationHandler for Ferese {
@@ -325,12 +344,14 @@ pub(crate) fn validate_size_constraints(toplevel: &ToplevelSurface) -> bool {
let mut cached = states.cached_state.get::<SurfaceCachedState>();
let state = cached.current();
let valid_axis = |min: i32, max: i32| min >= 0 && max >= 0 && (max == 0 || min <= max);
valid_axis(state.min_size.w, state.max_size.w) && valid_axis(state.min_size.h, state.max_size.h)
valid_axis(state.min_size.w, state.max_size.w)
&& valid_axis(state.min_size.h, state.max_size.h)
});
if !valid {
toplevel
.xdg_toplevel()
.post_error(xdg_toplevel::Error::InvalidSize, "invalid minimum/maximum window size");
toplevel.xdg_toplevel().post_error(
xdg_toplevel::Error::InvalidSize,
"invalid minimum/maximum window size",
);
}
valid
@@ -359,7 +380,9 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
if let Some(PopupKind::Xdg(popup)) = state.popups.find_popup(surface)
&& !popup.is_initial_configure_sent()
{
popup.send_configure().expect("initial popup configure is valid");
popup
.send_configure()
.expect("initial popup configure is valid");
}
}
@@ -415,10 +438,11 @@ impl Ferese {
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
return;
};
let window = self
.space
.elements()
.find(|window| window.toplevel().is_some_and(|toplevel| toplevel.wl_surface() == &root));
let window = self.space.elements().find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == &root)
});
let geometry = (|| {
if let Some(window) = window {
let id = *self.windows.ids().get(window)?;
@@ -432,11 +456,14 @@ impl Ferese {
Some((output_geometry, location))
} else {
let layer = self.space.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
let layer = self
.space
.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
self.space.outputs().find_map(|output| {
let output_geometry = self.space.output_geometry(output)?;
let layer_geometry = smithay::desktop::layer_map_for_output(output).layer_geometry(&layer)?;
let layer_geometry =
smithay::desktop::layer_map_for_output(output).layer_geometry(&layer)?;
Some((output_geometry, output_geometry.loc + layer_geometry.loc))
})
@@ -490,7 +517,9 @@ impl Dispatch<xdg_toplevel::XdgToplevel, XdgShellSurfaceUserData> for Ferese {
return;
}
smithay::wayland::Dispatch2::request(data, state, client, resource, request, dhandle, data_init);
smithay::wayland::Dispatch2::request(
data, state, client, resource, request, dhandle, data_init,
);
}
fn destroyed(
@@ -524,7 +553,11 @@ fn size_limits_usable(
(pending.max_size.w, pending.max_size.h),
)
});
let (min, max) = if minimum { (proposed, max) } else { (min, proposed) };
let (min, max) = if minimum {
(proposed, max)
} else {
(min, proposed)
};
(max.0 == 0 || min.0 <= max.0) && (max.1 == 0 || min.1 <= max.1)
}