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

+177 -60
View File
@@ -19,7 +19,10 @@ impl Ferese {
// desktop must not turn suspend/DPMS time into a spring step.
if self.space.outputs().next().is_none()
|| self.session_lock.sleeping
|| self.direct_backend.as_ref().is_some_and(|backend| !backend.active)
|| self
.direct_backend
.as_ref()
.is_some_and(|backend| !backend.active)
{
#[cfg(feature = "resize-metrics")]
self.resize_metrics.pause_clock(self.start_time.elapsed());
@@ -29,7 +32,13 @@ impl Ferese {
self.advance_animations_by(delta)
}
pub fn record_drm_presentation(&mut self, node: DrmNode, crtc: crtc::Handle, time: DrmEventTime, sequence: u32) {
pub fn record_drm_presentation(
&mut self,
node: DrmNode,
crtc: crtc::Handle,
time: DrmEventTime,
sequence: u32,
) {
if let Some(backend) = self.direct_backend.as_mut() {
backend.record_presentation(node, crtc, time, sequence);
}
@@ -37,7 +46,12 @@ impl Ferese {
#[cfg(feature = "resize-metrics")]
pub(super) fn measure_resize_pauses(&mut self, now: Duration) {
if self.session_lock.sleeping || self.direct_backend.as_ref().is_some_and(|backend| !backend.active) {
if self.session_lock.sleeping
|| self
.direct_backend
.as_ref()
.is_some_and(|backend| !backend.active)
{
self.resize_metrics.pause_clock(now);
return;
}
@@ -46,7 +60,8 @@ impl Ferese {
let Some(workspace) = self.workspaces.workspace_for_window(*id) else {
continue;
};
let moving = |value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
let moving =
|value: &AnimatedValue| value.current != value.target || value.velocity != 0.0;
let viewport = self
.presentation_dependencies
.viewport_blocked(workspace, &self.windows)
@@ -57,28 +72,46 @@ impl Ferese {
&& self
.output_workspaces
.output_for_workspace(workspace)
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(workspace))
&& self.focus_swipe().is_none_or(|swipe| swipe.workspace != workspace);
let others = self.workspaces.workspace(workspace).is_some_and(|workspace| {
workspace
.layout
.window_ids()
.chain(workspace.floating.iter().copied())
.any(|other| {
other != *id
&& self.presentation_dependencies.reflow_blocked(other, &self.windows)
&& self
.windows
.window(other)
.is_some_and(|window| self.space.element_location(window).is_some())
&& self.windows.record(other).is_some_and(|record| {
record.geometry.is_some_and(|mut geometry| {
geometry.advance(Duration::ZERO, self.spring_config, self.animations_enabled)
}) || record.world_x.as_ref().is_some_and(|(_, value)| moving(value))
|| record.coupled_width.as_ref().is_some_and(|(_, value)| moving(value))
})
.is_some_and(|output| {
self.output_workspaces.active_workspace(output) == Some(workspace)
})
});
&& self
.focus_swipe()
.is_none_or(|swipe| swipe.workspace != workspace);
let others = self
.workspaces
.workspace(workspace)
.is_some_and(|workspace| {
workspace
.layout
.window_ids()
.chain(workspace.floating.iter().copied())
.any(|other| {
other != *id
&& self
.presentation_dependencies
.reflow_blocked(other, &self.windows)
&& self.windows.window(other).is_some_and(|window| {
self.space.element_location(window).is_some()
})
&& self.windows.record(other).is_some_and(|record| {
record.geometry.is_some_and(|mut geometry| {
geometry.advance(
Duration::ZERO,
self.spring_config,
self.animations_enabled,
)
}) || record
.world_x
.as_ref()
.is_some_and(|(_, value)| moving(value))
|| record
.coupled_width
.as_ref()
.is_some_and(|(_, value)| moving(value))
})
})
});
self.resize_metrics.observe(
*id,
now,
@@ -110,7 +143,9 @@ impl Ferese {
if self.viewports.iter().any(|(workspace, viewport)| {
self.output_workspaces
.output_for_workspace(*workspace)
.is_some_and(|output| self.output_workspaces.active_workspace(output) == Some(*workspace))
.is_some_and(|output| {
self.output_workspaces.active_workspace(output) == Some(*workspace)
})
&& viewport.needs_tick()
}) {
return true;
@@ -124,7 +159,8 @@ impl Ferese {
self.windows.records().any(|(&id, record)| {
let focus = if selected == Some(id) { 1.0 } else { 0.0 };
let activation = crate::focus_effect::target(self.focus_effect, self.focused_window, id);
let activation =
crate::focus_effect::target(self.focus_effect, self.focused_window, id);
record_needs_tick(record, focus, activation, || {
self.windows
@@ -155,15 +191,20 @@ impl Ferese {
.filter(|swipe| !self.focus_swipe_is_current(swipe))
.map(|swipe| swipe.workspace)
{
let swipe = self.viewports.get_mut(&workspace).unwrap().end_gesture(None).unwrap();
let swipe = self
.viewports
.get_mut(&workspace)
.unwrap()
.end_gesture(None)
.unwrap();
self.presentation_dependencies
.restore_viewport(swipe.workspace, swipe.dependencies);
self.release_presentation_dependencies();
}
let mut active_animation = self
.tab_indicator
.advance(delta, self.spring_config, self.animations_enabled);
let mut active_animation =
self.tab_indicator
.advance(delta, self.spring_config, self.animations_enabled);
let mut completed_slides = Vec::new();
for (output, slide) in &mut self.workspace_slides {
if slide.held_progress.is_some() {
@@ -212,7 +253,9 @@ impl Ferese {
for (&id, record) in self.windows.records_mut() {
let selected = selected_window == Some(id);
let target = if selected { 1.0 } else { 0.0 };
let focus = record.focus.get_or_insert_with(|| AnimatedValue::new(target));
let focus = record
.focus
.get_or_insert_with(|| AnimatedValue::new(target));
let previous = focus.current;
let changed = focus.target != target;
focus.set_target(target);
@@ -225,7 +268,9 @@ impl Ferese {
focus.snap();
}
appearance_changed |= previous != focus.current;
let shadow = record.shadow.get_or_insert_with(|| AnimatedValue::new(target));
let shadow = record
.shadow
.get_or_insert_with(|| AnimatedValue::new(target));
let previous = shadow.current;
let changed = shadow.target != target;
shadow.set_target(target);
@@ -271,8 +316,9 @@ impl Ferese {
}
let mut finished = Vec::new();
self.render.closing.retain_mut(|window| {
let active =
self.animations_enabled && !self.session_lock.active() && window.advance(delta, self.spring_config);
let active = self.animations_enabled
&& !self.session_lock.active()
&& window.advance(delta, self.spring_config);
if !active {
finished.push(window.presentation.id);
}
@@ -299,8 +345,12 @@ impl Ferese {
#[cfg(feature = "resize-metrics")]
{
for (&id, record) in self.windows.records() {
if record.resize.is_some_and(|transaction| transaction.expired(now)) {
self.resize_metrics.end(id, now, crate::resize_metrics::End::Deadline);
if record
.resize
.is_some_and(|transaction| transaction.expired(now))
{
self.resize_metrics
.end(id, now, crate::resize_metrics::End::Deadline);
}
}
}
@@ -324,7 +374,9 @@ impl Ferese {
return false;
}
let waiting_for_client = self.presentation_dependencies.reflow_blocked(*id, &self.windows);
let waiting_for_client = self
.presentation_dependencies
.reflow_blocked(*id, &self.windows);
// A shrinking client's destination buffer arrives before the
// animated bounds reach it. Keep the old native pixels covering
// that strip rather than fading them into the neutral resize fill.
@@ -355,7 +407,11 @@ impl Ferese {
if !active {
#[cfg(feature = "resize-metrics")]
resize_metrics.handoff_end(*id, now, snapshot.elapsed);
tracing::debug!(?id, bytes = snapshot.bytes(), "released resize handoff snapshot");
tracing::debug!(
?id,
bytes = snapshot.bytes(),
"released resize handoff snapshot"
);
}
active
});
@@ -366,7 +422,9 @@ impl Ferese {
if self
.output_workspaces
.output_for_workspace(*workspace)
.is_none_or(|output| self.output_workspaces.active_workspace(output) != Some(*workspace))
.is_none_or(|output| {
self.output_workspaces.active_workspace(output) != Some(*workspace)
})
{
continue;
}
@@ -407,7 +465,8 @@ impl Ferese {
geometry.visual.velocity.width = 0.0;
}
if !held {
active_animation |= geometry.advance(delta, self.spring_config, self.animations_enabled);
active_animation |=
geometry.advance(delta, self.spring_config, self.animations_enabled);
}
if zooming != geometry.is_zooming() {
@@ -435,7 +494,12 @@ impl Ferese {
presented_world.velocity = 0.0;
}
let presented_viewport = viewport.presented_motion();
sync_scrolling_coordinates(geometry, &mut presented_world, &presented_viewport, false);
sync_scrolling_coordinates(
geometry,
&mut presented_world,
&presented_viewport,
false,
);
}
}
@@ -520,9 +584,9 @@ impl Ferese {
self.retarget_overview();
}
let overview_was_presenting = self.overview.is_presenting();
active_animation |= self
.overview
.advance(delta, self.spring_config, self.animations_enabled);
active_animation |=
self.overview
.advance(delta, self.spring_config, self.animations_enabled);
if overview_was_presenting != self.overview.is_presenting() {
self.reconcile_layer_keyboard_focus();
}
@@ -591,7 +655,10 @@ impl Ferese {
let position = slide.position(item, delta);
offsets.insert(
item.workspace,
(position.x * f64::from(size.w), position.y * f64::from(size.h)),
(
position.x * f64::from(size.w),
position.y * f64::from(size.h),
),
);
}
}
@@ -634,8 +701,14 @@ fn record_needs_tick(
) -> bool {
if record.resize.is_some()
|| record.opening.is_some()
|| record.focus.as_ref().is_none_or(|motion| motion.needs_update(focus))
|| record.shadow.as_ref().is_none_or(|motion| motion.needs_update(focus))
|| record
.focus
.as_ref()
.is_none_or(|motion| motion.needs_update(focus))
|| record
.shadow
.as_ref()
.is_none_or(|motion| motion.needs_update(focus))
{
return true;
}
@@ -645,7 +718,10 @@ fn record_needs_tick(
.focus_transition
.as_ref()
.is_none_or(|motion| motion.needs_update(activation))
|| record.world_x.as_ref().is_some_and(|(_, world)| moving(world))
|| record
.world_x
.as_ref()
.is_some_and(|(_, world)| moving(world))
|| record.coupled_width.is_some()
|| record.geometry.is_some_and(|geometry| {
geometry.is_zooming()
@@ -654,7 +730,8 @@ fn record_needs_tick(
|| geometry.visual.current != geometry.visual.target
|| geometry.visual.velocity != ferese_animation::RectVelocity::default()
|| (!record.natural_floating_pending
&& geometry.client.last_configured_size != Some(ClientSize::from_rect(geometry.logical)))
&& geometry.client.last_configured_size
!= Some(ClientSize::from_rect(geometry.logical)))
});
// A stale hidden target must not keep waking the whole desktop.
@@ -756,15 +833,33 @@ mod tests {
viewport.advance(Duration::from_millis(40), config, true, false);
let mut world = AnimatedValue::new(500.0);
world.set_target(900.0);
world.advance_with_policy(Duration::from_millis(40), config, CrossingPolicy::NoCrossing);
world.advance_with_policy(
Duration::from_millis(40),
config,
CrossingPolicy::NoCrossing,
);
let mut width = AnimatedValue::new(400.0);
width.set_target(700.0);
width.advance_with_policy(Duration::from_millis(40), config, CrossingPolicy::NoCrossing);
width.advance_with_policy(
Duration::from_millis(40),
config,
CrossingPolicy::NoCrossing,
);
let mut geometry = WindowGeometry::new(
Rect::new(world.current - viewport.motion().current, 0.0, width.current, 300.0),
Rect::new(
world.current - viewport.motion().current,
0.0,
width.current,
300.0,
),
None,
);
let target = Rect::new(world.target - viewport.motion().target, 0.0, width.target, 300.0);
let target = Rect::new(
world.target - viewport.motion().target,
0.0,
width.target,
300.0,
);
geometry.set_logical_target(target, Duration::from_millis(40));
sync_scrolling_coordinates(&mut geometry, &mut world, viewport.motion(), false);
geometry.visual.velocity.width = width.velocity;
@@ -773,12 +868,20 @@ mod tests {
world.retarget_preserving_motion(1000.0);
width.retarget_preserving_motion(600.0);
geometry.set_logical_target(
Rect::new(world.target - viewport.motion().target, 0.0, width.target, 300.0),
Rect::new(
world.target - viewport.motion().target,
0.0,
width.target,
300.0,
),
Duration::from_millis(40),
);
sync_scrolling_coordinates(&mut geometry, &mut world, viewport.motion(), false);
assert_eq!(geometry.visual.current, before);
assert_eq!(geometry.visual.velocity.x, world.velocity - viewport.motion().velocity);
assert_eq!(
geometry.visual.velocity.x,
world.velocity - viewport.motion().velocity
);
assert_eq!(geometry.logical.x, world.target - viewport.motion().target);
for mode in [
@@ -787,10 +890,17 @@ mod tests {
PresentationMode::Normal,
] {
let before = geometry.visual.current;
geometry.set_presentation_mode(Rect::new(0.0, 0.0, 1920.0, 1080.0), mode, Duration::from_millis(40));
geometry.set_presentation_mode(
Rect::new(0.0, 0.0, 1920.0, 1080.0),
mode,
Duration::from_millis(40),
);
sync_scrolling_coordinates(&mut geometry, &mut world, viewport.motion(), true);
assert_eq!(geometry.visual.current, before);
assert_eq!(world.current, geometry.visual.current.x + viewport.motion().current);
assert_eq!(
world.current,
geometry.visual.current.x + viewport.motion().current
);
geometry.advance(Duration::ZERO, config, true);
assert_eq!(geometry.visual.current, before);
geometry.advance(Duration::from_millis(16), config, true);
@@ -870,7 +980,10 @@ mod tests {
state.register_output(&output, "pause-test".into());
let workspace = state.workspaces.active_id();
let id = WindowId(1);
state.workspaces.insert_window(id, Axis::Horizontal, 0.5).unwrap();
state
.workspaces
.insert_window(id, Axis::Horizontal, 0.5)
.unwrap();
state.windows.records.insert(id, settled());
let mut viewport = AnimatedValue::new(0.0);
viewport.set_target(500.0);
@@ -960,7 +1073,11 @@ mod tests {
// A settled transaction still has to flush its resume guard; otherwise
// the next unrelated animation would inherit an old paused workspace.
let target = state.viewports[&workspace].motion().target;
state.viewports.get_mut(&workspace).unwrap().retarget(target, false);
state
.viewports
.get_mut(&workspace)
.unwrap()
.retarget(target, false);
state.windows.record_mut(id).unwrap().focus = Some(AnimatedValue::new(0.0));
state.windows.set_transaction(
id,
+9 -2
View File
@@ -37,7 +37,10 @@ impl Ferese {
data.title.as_deref(),
data.parent.is_some(),
);
(rule.block_out_from_screencasts == Some(true), data.parent.clone())
(
rule.block_out_from_screencasts == Some(true),
data.parent.clone(),
)
});
if blocked {
return true;
@@ -84,7 +87,11 @@ impl Ferese {
&& self.capture_protected(window)
});
}
let Some(mut focus) = self.seat.get_pointer().and_then(|pointer| pointer.current_focus()) else {
let Some(mut focus) = self
.seat
.get_pointer()
.and_then(|pointer| pointer.current_focus())
else {
return false;
};
while let Some(parent) = get_parent(&focus) {
+352 -83
View File
@@ -22,7 +22,13 @@ use smithay::wayland::shell::xdg::{XdgShellHandler, XdgToplevelSurfaceData, XdgW
use crate::Ferese;
use crate::state::{ClientState, DesktopOutput};
pub(crate) fn request(wire: &mut UnixStream, object: u32, opcode: u32, args: &[u32], fd: Option<RawFd>) {
pub(crate) fn request(
wire: &mut UnixStream,
object: u32,
opcode: u32,
args: &[u32],
fd: Option<RawFd>,
) {
let bytes = [object, (((args.len() + 2) * 4) as u32) << 16 | opcode]
.into_iter()
.chain(args.iter().copied())
@@ -47,7 +53,10 @@ pub(crate) fn request(wire: &mut UnixStream, object: u32, opcode: u32, args: &[u
(*header).cmsg_type = libc::SCM_RIGHTS;
(*header).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<RawFd>() as u32) as usize;
libc::CMSG_DATA(header).cast::<RawFd>().write(fd);
assert_eq!(libc::sendmsg(wire.as_raw_fd(), &msg, 0), bytes.len() as isize);
assert_eq!(
libc::sendmsg(wire.as_raw_fd(), &msg, 0),
bytes.len() as isize
);
}
} else {
wire.write_all(&bytes).unwrap();
@@ -70,7 +79,9 @@ pub(crate) fn ack_configure(wire: &mut UnixStream, xdg: u32) -> u32 {
let object = u32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap());
let header = u32::from_ne_bytes(bytes[offset + 4..offset + 8].try_into().unwrap());
if object == xdg && header & 0xffff == 0 {
serial = Some(u32::from_ne_bytes(bytes[offset + 8..offset + 12].try_into().unwrap()));
serial = Some(u32::from_ne_bytes(
bytes[offset + 8..offset + 12].try_into().unwrap(),
));
}
offset += (header >> 16) as usize;
}
@@ -79,7 +90,11 @@ pub(crate) fn ack_configure(wire: &mut UnixStream, xdg: u32) -> u32 {
serial.unwrap()
}
pub(crate) fn window(state: &mut Ferese, events: &mut EventLoop<'static, Ferese>, color: u32) -> (Window, UnixStream) {
pub(crate) fn window(
state: &mut Ferese,
events: &mut EventLoop<'static, Ferese>,
color: u32,
) -> (Window, UnixStream) {
let (server, mut wire) = UnixStream::pair().unwrap();
let client = state
.display_handle
@@ -93,7 +108,11 @@ pub(crate) fn window(state: &mut Ferese, events: &mut EventLoop<'static, Ferese>
)
.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, smithay::wayland::GlobalData, Ferese>(
@@ -109,7 +128,8 @@ pub(crate) fn window(state: &mut Ferese, events: &mut EventLoop<'static, Ferese>
dispatch(events, state);
ack_configure(&mut wire, 3);
let mut file = tempfile::tempfile().unwrap();
file.write_all(&color.to_ne_bytes().repeat(64 * 48)).unwrap();
file.write_all(&color.to_ne_bytes().repeat(64 * 48))
.unwrap();
request(
&mut wire,
shm.id().protocol_id(),
@@ -169,7 +189,11 @@ fn attach_children(
request(wire, 7, 6, &[], None);
request(wire, 2, 6, &[], None);
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();
request(wire, compositor.id().protocol_id(), 0, &[9], None);
request(wire, shell.id().protocol_id(), 2, &[10, 9], None);
@@ -185,7 +209,8 @@ fn attach_children(
request(wire, 9, 6, &[], None);
dispatch(events, state);
assert_eq!(
smithay::desktop::PopupManager::popups_for_surface(window.toplevel().unwrap().wl_surface()).count(),
smithay::desktop::PopupManager::popups_for_surface(window.toplevel().unwrap().wl_surface())
.count(),
1
);
}
@@ -224,7 +249,13 @@ fn pixels(
Rectangle::from_size((320, 240).into()),
));
let mut target = Some(texture.clone());
crate::backends::direct::capture::capture_output(state, renderer, &mut target, output, &scene);
crate::backends::direct::capture::capture_output(
state,
renderer,
&mut target,
output,
&scene,
);
let mut result = receiver.try_recv().unwrap().result.unwrap().pixels;
for pixel in result.chunks_exact_mut(4) {
pixel.swap(0, 2);
@@ -235,7 +266,11 @@ fn pixels(
let mut target = renderer.bind(texture).unwrap();
crate::render::redraw_output(renderer, &mut target, output, &elements).unwrap();
let mapping = renderer
.copy_framebuffer(&target, Rectangle::from_size((320, 240).into()), Fourcc::Abgr8888)
.copy_framebuffer(
&target,
Rectangle::from_size((320, 240).into()),
Fourcc::Abgr8888,
)
.unwrap();
renderer.map_texture(&mapping).unwrap().to_vec()
}
@@ -300,28 +335,47 @@ fn lock_scene_draws_the_pointer_and_delivers_client_cursor_frames() {
smithay::input::pointer::CursorIcon::Default,
crate::cursor::fallback_cursor(),
);
state.seat.get_pointer().unwrap().set_location((20.0, 20.0).into());
state
.seat
.get_pointer()
.unwrap()
.set_location((20.0, 20.0).into());
let device = EGLDevice::enumerate().unwrap().last().expect("EGL device");
let display = unsafe { EGLDisplay::new(device).unwrap() };
let mut renderer = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
let mut texture =
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into()).unwrap();
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
.unwrap();
for scale in [1.0, 1.75] {
output.change_current_state(None, None, Some(Scale::Fractional(scale)), None);
for include_cursor in [false, true] {
let scene = state.sample_frame(&output, Duration::ZERO);
let elements =
crate::render::sampled_output_elements(&mut state, &mut renderer, &output, include_cursor, &scene);
let elements = crate::render::sampled_output_elements(
&mut state,
&mut renderer,
&output,
include_cursor,
&scene,
);
let mut target = renderer.bind(&mut texture).unwrap();
crate::render::redraw_output(&mut renderer, &mut target, &output, &elements).unwrap();
let mapping = renderer
.copy_framebuffer(&target, Rectangle::from_size((320, 240).into()), Fourcc::Abgr8888)
.copy_framebuffer(
&target,
Rectangle::from_size((320, 240).into()),
Fourcc::Abgr8888,
)
.unwrap();
let bytes = renderer.map_texture(&mapping).unwrap();
let white = bytes.chunks_exact(4).any(|pixel| pixel == [255, 255, 255, 255]);
assert_eq!(white, include_cursor, "cursor above the dim overlay at scale {scale}");
let white = bytes
.chunks_exact(4)
.any(|pixel| pixel == [255, 255, 255, 255]);
assert_eq!(
white, include_cursor,
"cursor above the dim overlay at scale {scale}"
);
assert!(
bytes
.chunks_exact(4)
@@ -349,15 +403,25 @@ fn lock_scene_draws_the_pointer_and_delivers_client_cursor_frames() {
let seat = state.seat.clone();
state.cursor_image(&seat, CursorImageStatus::Surface(cursor.clone()));
let scene = state.sample_frame(&output, Duration::ZERO);
let elements = crate::render::sampled_output_elements(&mut state, &mut renderer, &output, true, &scene);
let elements =
crate::render::sampled_output_elements(&mut state, &mut renderer, &output, true, &scene);
let mut target = renderer.bind(&mut texture).unwrap();
let mut tracker = OutputDamageTracker::from_output(&output);
let rendered = tracker
.render_output(&mut renderer, &mut target, 0, &elements, [0.0, 0.0, 0.0, 1.0])
.render_output(
&mut renderer,
&mut target,
0,
&elements,
[0.0, 0.0, 0.0, 1.0],
)
.unwrap();
state.display_presentation.queued(&output, &rendered.states);
state.display_presentation.presented(&output);
assert_eq!(state.callback_outputs().get(&(&cursor).into()), Some(&output));
assert_eq!(
state.callback_outputs().get(&(&cursor).into()),
Some(&output)
);
state.lock_frame_callbacks(&output);
state.display_handle.flush_clients().unwrap();
wire.set_nonblocking(true).unwrap();
@@ -371,7 +435,10 @@ fn lock_scene_draws_the_pointer_and_delivers_client_cursor_frames() {
callback_done |= object == 9 && header & 0xffff == 0;
offset += (header >> 16) as usize;
}
assert!(callback_done, "the lock owner's cursor must receive its next frame");
assert!(
callback_done,
"the lock owner's cursor must receive its next frame"
);
}
#[test]
@@ -443,9 +510,11 @@ fn capture_privacy_pixels_and_policy_transitions() {
let context = EGLContext::new(&display).unwrap();
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
let mut display_texture =
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into()).unwrap();
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
.unwrap();
let mut capture_texture =
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into()).unwrap();
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
.unwrap();
let has_red = |bytes: &[u8]| bytes.chunks_exact(4).any(|pixel| pixel == [255, 0, 0, 255]);
assert!(has_red(&pixels(
&mut state,
@@ -475,11 +544,23 @@ fn capture_privacy_pixels_and_policy_transitions() {
assert!(!state.capture_window_allowed(id, epoch));
crate::handlers::window_capture::tests::check_deferred_snapshot(&state, &secret, epoch, false);
assert!(
!has_red(&pixels(&mut state, &mut renderer, &output, true, &mut capture_texture)),
!has_red(&pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture
)),
"reused capture target leaked previous pixels"
);
assert!(
has_red(&pixels(&mut state, &mut renderer, &output, false, &mut display_texture)),
has_red(&pixels(
&mut state,
&mut renderer,
&output,
false,
&mut display_texture
)),
"display lost protected content"
);
app_id(&mut state, &secret, "ordinary");
@@ -496,11 +577,12 @@ fn capture_privacy_pixels_and_policy_transitions() {
true,
&mut capture_texture
)));
let rules =
crate::config::Config::parse_source("window-rule app-id=\"ordinary\" block-out-from-screencasts=#true\n")
.unwrap()
.window_rules()
.unwrap();
let rules = crate::config::Config::parse_source(
"window-rule app-id=\"ordinary\" block-out-from-screencasts=#true\n",
)
.unwrap()
.window_rules()
.unwrap();
state.window_rules = rules;
state.refresh_capture_privacy();
assert!(state.capture_protected(&secret));
@@ -522,18 +604,40 @@ fn capture_privacy_pixels_and_policy_transitions() {
Transform::Flipped270,
] {
for scale in [1.0, 1.25] {
output.change_current_state(None, Some(transform), Some(Scale::Fractional(scale)), None);
output.change_current_state(
None,
Some(transform),
Some(Scale::Fractional(scale)),
None,
);
assert!(
has_red(&pixels(&mut state, &mut renderer, &output, false, &mut display_texture)),
has_red(&pixels(
&mut state,
&mut renderer,
&output,
false,
&mut display_texture
)),
"display {transform:?}/{scale}"
);
assert!(
!has_red(&pixels(&mut state, &mut renderer, &output, true, &mut capture_texture)),
!has_red(&pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture
)),
"capture {transform:?}/{scale}"
);
}
}
output.change_current_state(None, Some(Transform::Normal), Some(Scale::Fractional(1.0)), None);
output.change_current_state(
None,
Some(Transform::Normal),
Some(Scale::Fractional(1.0)),
None,
);
// An otherwise unprotected child still inherits its parent's protection.
let (child, _child_wire) = window(&mut state, &mut events, 0xff00ff00);
with_states(child.toplevel().unwrap().wl_surface(), |states| {
@@ -547,14 +651,34 @@ fn capture_privacy_pixels_and_policy_transitions() {
});
state.parent_changed(child.toplevel().unwrap().clone());
assert!(state.capture_protected(&child));
let captured = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
let captured = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
assert!(!has_red(&captured));
assert!(!captured.chunks_exact(4).any(|pixel| pixel == [0, 255, 0, 255]));
assert!(
!captured
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
);
state.set_overview_active(true);
let overview = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
let overview = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
assert!(!has_red(&overview));
assert!(!overview.chunks_exact(4).any(|pixel| pixel == [0, 255, 0, 255]));
assert!(
!overview
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
);
state.set_overview_active(false);
// Region requests go through the same filtered output readback.
@@ -628,19 +752,37 @@ fn capture_privacy_pixels_and_policy_transitions() {
state.windows.set_geometry(child_id, geometry);
state.space.map_element(child.clone(), (440, 40), false);
assert!(
pixels(&mut state, &mut renderer, &second, false, &mut display_texture)
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
pixels(
&mut state,
&mut renderer,
&second,
false,
&mut display_texture
)
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
);
assert!(
!pixels(&mut state, &mut renderer, &second, true, &mut capture_texture)
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
!pixels(
&mut state,
&mut renderer,
&second,
true,
&mut capture_texture
)
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255])
);
// Unprotected content still captures, including translucent pixels.
let (public, mut public_wire) = window(&mut state, &mut events, 0x80000080);
let captured = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
let captured = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
assert!(captured.chunks_exact(4).any(|pixel| pixel[2] > 100));
assert!(!has_red(&captured));
@@ -650,7 +792,13 @@ fn capture_privacy_pixels_and_policy_transitions() {
let manager = client
.create_resource::<FereseMaterialManagerV1, (), Ferese>(&state.display_handle, 1, ())
.unwrap();
request(&mut public_wire, manager.id().protocol_id(), 1, &[7, 2], None);
request(
&mut public_wire,
manager.id().protocol_id(),
1,
&[7, 2],
None,
);
dispatch(&mut events, &mut state);
state.theme_settings.material_style = crate::config::MaterialStyle::Translucent;
state.theme_settings.backdrop_blur = 12.0;
@@ -658,17 +806,49 @@ fn capture_privacy_pixels_and_policy_transitions() {
let public_id = state.windows.ids()[&public];
let secret_geometry = *state.windows.geometry(&id).unwrap();
state.windows.set_geometry(public_id, secret_geometry);
state
.space
.map_element(public.clone(), state.space.element_location(&secret).unwrap(), true);
let before = pixels(&mut state, &mut renderer, &output, false, &mut display_texture);
let protected = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
state.space.map_element(
public.clone(),
state.space.element_location(&secret).unwrap(),
true,
);
let before = pixels(
&mut state,
&mut renderer,
&output,
false,
&mut display_texture,
);
let protected = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
state.space.unmap_elem(&secret);
state.space.unmap_elem(&child);
let absent = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
assert_eq!(protected, absent, "protected content leaked through a cached material");
let without = pixels(&mut state, &mut renderer, &output, false, &mut display_texture);
assert_ne!(before, without, "fixture must place protected content behind glass");
let absent = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
assert_eq!(
protected, absent,
"protected content leaked through a cached material"
);
let without = pixels(
&mut state,
&mut renderer,
&output,
false,
&mut display_texture,
);
assert_ne!(
before, without,
"fixture must place protected content behind glass"
);
// A stationary client's cursor is a separate surface and can outlive its
// protected toplevel. Never reveal that retained image after an opt-out or close.
@@ -682,7 +862,13 @@ fn capture_privacy_pixels_and_policy_transitions() {
smithay::wayland::GlobalData,
)
.unwrap();
request(&mut secret_wire, compositor.id().protocol_id(), 0, &[13], None);
request(
&mut secret_wire,
compositor.id().protocol_id(),
0,
&[13],
None,
);
request(&mut secret_wire, 13, 1, &[6, 0, 0], None);
request(&mut secret_wire, 13, 6, &[], None);
dispatch(&mut events, &mut state);
@@ -708,7 +894,9 @@ fn capture_privacy_pixels_and_policy_transitions() {
.unwrap();
state.remove_tiled_window(&secret);
assert!(state.capture_cursor_protected());
assert!(matches!(&state.cursor_status, CursorImageStatus::Surface(current) if current == &cursor));
assert!(
matches!(&state.cursor_status, CursorImageStatus::Surface(current) if current == &cursor)
);
state.cursor_image(&seat, CursorImageStatus::Hidden);
assert!(!state.capture_cursor_protected());
assert!(!state.has_capture_exclusions());
@@ -825,14 +1013,19 @@ fn resize_dependencies_protocol_and_pixels() {
else {
panic!()
};
layout.set_column_width(id, ColumnWidth::Fixed(width)).unwrap();
layout
.set_column_width(id, ColumnWidth::Fixed(width))
.unwrap();
state.relayout();
state.display_handle.flush_clients().unwrap();
};
set_width(&mut state, b_id, 240.0);
let b_serial = ack_configure(&mut b_wire, 3);
dispatch(&mut events, &mut state);
assert!(state.windows.transaction(&b_id).is_some(), "ack alone must not release");
assert!(
state.windows.transaction(&b_id).is_some(),
"ack alone must not release"
);
// This target uses the first column, independently of the pending later width.
let bounds = Rect::new(0.0, 0.0, 320.0, 240.0);
state
@@ -849,21 +1042,43 @@ fn resize_dependencies_protocol_and_pixels() {
before,
"independent viewport stalled before delayed buffer commit"
);
assert_eq!(state.windows.geometry(&b_id).unwrap().visual.current.width, held_size);
assert_eq!(state.windows.record(b_id).unwrap().world_x.unwrap().1, held_world);
assert_eq!(
state.windows.geometry(&b_id).unwrap().visual.current.width,
held_size
);
assert_eq!(
state.windows.record(b_id).unwrap().world_x.unwrap().1,
held_world
);
// BASELINE_REPRO_END: the stage above also runs against the reviewed base.
assert!(
!state
.presentation_dependencies
.viewport_blocked(workspace, &state.windows)
);
assert!(state.presentation_dependencies.reflow_blocked(b_id, &state.windows));
assert!(state.presentation_dependencies.reflow_blocked(c_id, &state.windows));
assert!(!state.presentation_dependencies.reflow_blocked(a_id, &state.windows));
assert!(
state
.presentation_dependencies
.reflow_blocked(b_id, &state.windows)
);
assert!(
state
.presentation_dependencies
.reflow_blocked(c_id, &state.windows)
);
assert!(
!state
.presentation_dependencies
.reflow_blocked(a_id, &state.windows)
);
let geometry = *state.windows.geometry(&b_id).unwrap();
let position = state.space.element_location(&b).unwrap();
assert_eq!(position.x, geometry.visual.current.x.round() as i32);
let point = (geometry.visual.current.x + 5.0, geometry.visual.current.y + 5.0).into();
let point = (
geometry.visual.current.x + 5.0,
geometry.visual.current.y + 5.0,
)
.into();
let (hit, origin) = state.window_surface_under(point).unwrap();
assert_eq!(hit, *b.toplevel().unwrap().wl_surface());
assert!((origin.x - geometry.visual.current.x).abs() < 0.001);
@@ -894,28 +1109,52 @@ fn resize_dependencies_protocol_and_pixels() {
let context = EGLContext::new(&display).unwrap();
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
let mut display_texture =
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into()).unwrap();
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
.unwrap();
let mut capture_texture =
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into()).unwrap();
Offscreen::<GlesTexture>::create_buffer(&mut renderer, Fourcc::Abgr8888, (320, 240).into())
.unwrap();
// Offscreen fixture explicitly captures the existing native frame; production
// capture-before-commit is exercised separately in the privacy regression.
let snapshot =
crate::render::capture_resize_snapshot(&mut renderer, &b, b.geometry(), 1.0, 16 * 1024 * 1024).unwrap();
state.render.set_snapshot(b_id, snapshot.expect("native snapshot"));
let snapshot = crate::render::capture_resize_snapshot(
&mut renderer,
&b,
b.geometry(),
1.0,
16 * 1024 * 1024,
)
.unwrap();
state
.render
.set_snapshot(b_id, snapshot.expect("native snapshot"));
app_id(&mut state, &b, "resize.secret");
state.refresh_capture_privacy();
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(16));
let actual = state.windows.geometry(&b_id).unwrap().visual.current;
let displayed = pixels(&mut state, &mut renderer, &output, false, &mut display_texture);
let displayed = pixels(
&mut state,
&mut renderer,
&output,
false,
&mut display_texture,
);
let at = ((actual.y.round() as usize + 5) * 320 + actual.x.round() as usize + 5) * 4;
assert_eq!(
&displayed[at..at + 4],
&[0, 255, 0, 255],
"live/snapshot pixels did not translate with geometry"
);
let captured = pixels(&mut state, &mut renderer, &output, true, &mut capture_texture);
let captured = pixels(
&mut state,
&mut renderer,
&output,
true,
&mut capture_texture,
);
assert!(
!captured.chunks_exact(4).any(|pixel| pixel == [0, 255, 0, 255]),
!captured
.chunks_exact(4)
.any(|pixel| pixel == [0, 255, 0, 255]),
"translated protected snapshot leaked into capture"
);
assert!(state.render.snapshot(&b_id).is_some());
@@ -983,7 +1222,9 @@ fn resize_dependencies_protocol_and_pixels() {
)
.unwrap();
let mut resized = tempfile::tempfile().unwrap();
resized.write_all(&0xff00ff00u32.to_ne_bytes().repeat(32 * 48)).unwrap();
resized
.write_all(&0xff00ff00u32.to_ne_bytes().repeat(32 * 48))
.unwrap();
request(
&mut b_wire,
shm.id().protocol_id(),
@@ -1008,11 +1249,21 @@ fn resize_dependencies_protocol_and_pixels() {
// A hidden workspace must not impose its viewport wait on its replacement.
let other = state.workspaces.create_workspace();
let output_id = state.output_id(&output).unwrap();
state.output_workspaces.assign_workspace(output_id, other).unwrap();
state
.output_workspaces
.assign_workspace(output_id, other)
.unwrap();
state.workspaces.activate(other).unwrap();
state.relayout();
assert!(!state.presentation_dependencies.viewport_blocked(other, &state.windows));
state.output_workspaces.assign_workspace(output_id, workspace).unwrap();
assert!(
!state
.presentation_dependencies
.viewport_blocked(other, &state.windows)
);
state
.output_workspaces
.assign_workspace(output_id, workspace)
.unwrap();
state.workspaces.activate(workspace).unwrap();
state.focused_window = Some(a_id);
state.relayout();
@@ -1090,7 +1341,9 @@ fn resize_dependencies_protocol_and_pixels() {
set_width(&mut state, d_id, 64.0);
state.focused_window = Some(d_id);
{
let WorkspaceLayout::Scrolling(layout) = &mut state.workspaces.workspace_mut(workspace).unwrap().layout else {
let WorkspaceLayout::Scrolling(layout) =
&mut state.workspaces.workspace_mut(workspace).unwrap().layout
else {
panic!()
};
layout.set_focus_strategy(ViewportFocusStrategy::Paged);
@@ -1130,7 +1383,9 @@ fn resize_dependencies_protocol_and_pixels() {
state.animations_enabled = false;
state.advance_animations_at(Duration::ZERO, Duration::from_secs(10));
{
let WorkspaceLayout::Scrolling(layout) = &mut state.workspaces.workspace_mut(workspace).unwrap().layout else {
let WorkspaceLayout::Scrolling(layout) =
&mut state.workspaces.workspace_mut(workspace).unwrap().layout
else {
panic!()
};
layout.move_into_column(d_id, b_id).unwrap();
@@ -1165,7 +1420,10 @@ fn resize_dependencies_protocol_and_pixels() {
);
let before = state.windows.geometry(&d_id).unwrap().visual.current;
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(10016));
assert_eq!(state.windows.geometry(&d_id).unwrap().visual.current, before);
assert_eq!(
state.windows.geometry(&d_id).unwrap().visual.current,
before
);
request(&mut d_wire, 2, 1, &[6, 0, 0], None);
request(&mut d_wire, 2, 6, &[], None);
@@ -1178,7 +1436,11 @@ fn resize_dependencies_protocol_and_pixels() {
state.windows.transaction(&b_id).is_some(),
"other tile must still be pending"
);
assert!(!state.presentation_dependencies.reflow_blocked(d_id, &state.windows));
assert!(
!state
.presentation_dependencies
.reflow_blocked(d_id, &state.windows)
);
assert!(state.windows.geometry(&d_id).unwrap().is_zooming());
// Consume the release tick without charging the preceding client wait.
state.advance_animations_at(Duration::ZERO, Duration::from_millis(10016));
@@ -1199,8 +1461,15 @@ fn resize_dependencies_protocol_and_pixels() {
dispatch(&mut events, &mut state);
assert!(state.windows.transaction(&d_id).is_none());
assert!(state.windows.transaction(&b_id).is_some());
assert!(state.presentation_dependencies.reflow_blocked(d_id, &state.windows));
assert!(
state
.presentation_dependencies
.reflow_blocked(d_id, &state.windows)
);
let returning = state.windows.geometry(&d_id).unwrap().visual.current;
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(10048));
assert_eq!(state.windows.geometry(&d_id).unwrap().visual.current, returning);
assert_eq!(
state.windows.geometry(&d_id).unwrap().visual.current,
returning
);
}
+78 -20
View File
@@ -1,14 +1,22 @@
use super::*;
impl Ferese {
pub(super) fn adjust_floating_direction(&mut self, id: WindowId, direction: Direction, resize: bool) {
pub(super) fn adjust_floating_direction(
&mut self,
id: WindowId,
direction: Direction,
resize: bool,
) {
const STEP: f64 = 32.0;
let workspace = self.workspaces.active();
if self.session_lock.active()
|| self.input_capture.captures(1)
|| self.workspaces.workspace_for_window(id) != Some(workspace.id)
|| workspace.fullscreen == Some(id)
|| self.windows.record(id).is_some_and(|record| record.maximized)
|| self
.windows
.record(id)
.is_some_and(|record| record.maximized)
{
return;
}
@@ -32,7 +40,10 @@ impl Ferese {
if resize {
destination = constrained_floating_rect(
destination,
self.window_constraints().get(&id).copied().unwrap_or_default(),
self.window_constraints()
.get(&id)
.copied()
.unwrap_or_default(),
);
}
destination = crate::floating::clamp_to_work(destination, work);
@@ -91,7 +102,10 @@ impl Ferese {
.automatic_axis(Some(window), bounds)
.unwrap_or(Axis::Horizontal);
if let Err(error) = self.workspaces.toggle_floating(window, floating_rect, axis, 0.5) {
if let Err(error) = self
.workspaces
.toggle_floating(window, floating_rect, axis, 0.5)
{
tracing::error!(%error, ?window, "failed to toggle floating window");
return;
}
@@ -142,7 +156,11 @@ impl Ferese {
&& let WorkspaceLayout::Scrolling(layout) = &mut workspace.layout
{
if enabled {
if let Some(column) = layout.columns().iter().find(|column| column.windows.contains(&window)) {
if let Some(column) = layout
.columns()
.iter()
.find(|column| column.windows.contains(&window))
{
self.windows.update(window, |w| {
w.maximized_column_width.get_or_insert(column.width);
});
@@ -177,7 +195,12 @@ impl Ferese {
layer != Layer::Top || !self.output_has_fullscreen(output)
}
pub(crate) fn layer_visible_for_frame(&self, output: &Output, layer: Layer, overview: bool) -> bool {
pub(crate) fn layer_visible_for_frame(
&self,
output: &Output,
layer: Layer,
overview: bool,
) -> bool {
layer != Layer::Top || !self.output_has_fullscreen_for_frame(output, overview)
}
@@ -208,7 +231,9 @@ impl Ferese {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
self.windows.update(id, |w| w.resize_anchor.take()).flatten();
self.windows
.update(id, |w| w.resize_anchor.take())
.flatten();
let rect = Rect::new(
location.x as f64,
location.y as f64,
@@ -224,8 +249,11 @@ impl Ferese {
// Direct manipulation follows the pointer, including while the client is
// still drawing its next buffer. Rendering and hit testing share this rect.
#[cfg(feature = "resize-metrics")]
self.resize_metrics
.end(id, self.start_time.elapsed(), crate::resize_metrics::End::Cancelled);
self.resize_metrics.end(
id,
self.start_time.elapsed(),
crate::resize_metrics::End::Cancelled,
);
self.windows.clear_transaction(&id);
self.render.clear_snapshot(&id);
let Some(record) = self.windows.record_mut(id) else {
@@ -248,7 +276,8 @@ impl Ferese {
toplevel.send_pending_configure();
}
self.windows.update(id, |record| record.world_x = None);
self.windows.update(id, |record| record.coupled_width = None);
self.windows
.update(id, |record| record.coupled_width = None);
self.map_window_geometry(window.clone(), location);
self.sync_window_stacking();
// Move/resize/cancel callbacks run with Smithay's pointer mutex held.
@@ -268,14 +297,19 @@ impl Ferese {
}
pub(super) fn output_bounds(&self) -> Option<Rect> {
let output = self.focused_output().or_else(|| self.space.outputs().next())?;
let output = self
.focused_output()
.or_else(|| self.space.outputs().next())?;
self.output_bounds_for(output)
}
pub(super) fn floating_outputs(&self) -> Vec<(String, Rect)> {
self.space
.outputs()
.filter_map(|output| self.output_bounds_for(output).map(|work| (output.name(), work)))
.filter_map(|output| {
self.output_bounds_for(output)
.map(|work| (output.name(), work))
})
.collect()
}
@@ -294,7 +328,11 @@ impl Ferese {
.unwrap_or_default()
}
pub(super) fn floating_obstacles(&self, exclude: Option<WindowId>, work: Rect) -> Vec<(u64, Rect)> {
pub(super) fn floating_obstacles(
&self,
exclude: Option<WindowId>,
work: Rect,
) -> Vec<(u64, Rect)> {
self.space
.elements()
.filter_map(|window| {
@@ -312,7 +350,10 @@ impl Ferese {
.workspace(workspace)?
.fullscreen
.is_some_and(|full| full != id)
&& !matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }))
&& !matches!(
self.workspaces.placement(id),
Some(WindowPlacement::Floating { .. })
)
{
return None;
}
@@ -347,7 +388,11 @@ impl Ferese {
.and_then(|rect| intersection(rect, work));
let dialog = crate::window_rules::is_native_dialog(app.as_deref());
let remembered = id
.and_then(|id| self.windows.record(id).and_then(|w| w.floating_memory.as_ref()))
.and_then(|id| {
self.windows
.record(id)
.and_then(|w| w.floating_memory.as_ref())
})
.or_else(|| {
if parent.is_none() {
app.as_deref().and_then(|app| self.floating_memory.get(app))
@@ -384,7 +429,12 @@ impl Ferese {
size
};
let sized = constrained_floating_rect(
Rect::new(0., 0., width.unwrap_or(stored_size.0), height.unwrap_or(stored_size.1)),
Rect::new(
0.,
0.,
width.unwrap_or(stored_size.0),
height.unwrap_or(stored_size.1),
),
constraints,
);
let dimensions = (sized.width, sized.height);
@@ -438,7 +488,9 @@ impl Ferese {
.floating_outputs()
.into_iter()
.max_by(|(_, a), (_, b)| {
let area = |work| crate::floating::intersection(rect, work).map_or(0., |r| r.width * r.height);
let area = |work| {
crate::floating::intersection(rect, work).map_or(0., |r| r.width * r.height)
};
area(*a).total_cmp(&area(*b))
})
.filter(|(_, work)| crate::floating::intersection(rect, *work).is_some())
@@ -446,7 +498,8 @@ impl Ferese {
return;
};
let entry = crate::floating::Remembered::new(output, rect, work);
self.windows.update(id, |w| w.floating_memory = Some(entry.clone()));
self.windows
.update(id, |w| w.floating_memory = Some(entry.clone()));
if let Some(app) = app {
self.floating_memory.save(app, entry);
}
@@ -462,13 +515,18 @@ impl Ferese {
&self,
window: &Window,
raw: Rect,
) -> (Vec<crate::floating::SnapLine>, Vec<crate::floating::SnapLine>) {
) -> (
Vec<crate::floating::SnapLine>,
Vec<crate::floating::SnapLine>,
) {
let id = self.windows.ids().get(window).copied();
let work = self
.floating_outputs()
.into_iter()
.max_by(|(_, a), (_, b)| {
let area = |work| crate::floating::intersection(raw, work).map_or(0., |r| r.width * r.height);
let area = |work| {
crate::floating::intersection(raw, work).map_or(0., |r| r.width * r.height)
};
area(*a).total_cmp(&area(*b))
})
.filter(|(_, work)| crate::floating::intersection(raw, *work).is_some())
+49 -14
View File
@@ -56,9 +56,17 @@ impl Ferese {
.find(|id| {
self.workspaces.workspace_for_window(*id) == Some(workspace)
&& matches!(
(self.workspaces.placement(current), self.workspaces.placement(*id)),
(Some(WindowPlacement::Tiled), Some(WindowPlacement::Floating { .. }))
| (Some(WindowPlacement::Floating { .. }), Some(WindowPlacement::Tiled))
(
self.workspaces.placement(current),
self.workspaces.placement(*id)
),
(
Some(WindowPlacement::Tiled),
Some(WindowPlacement::Floating { .. })
) | (
Some(WindowPlacement::Floating { .. }),
Some(WindowPlacement::Tiled)
)
)
});
if let Some(id) = target {
@@ -74,7 +82,9 @@ impl Ferese {
let available = self.focus_candidates();
if !preview {
self.cancel_focus_cycle();
let order = self.focus_history.candidates(self.focused_window, &available);
let order = self
.focus_history
.candidates(self.focused_window, &available);
let mut cycle = ferese_core::FocusCycle::new(order, self.focused_window);
if let Some(id) = cycle.advance(reverse, &available) {
self.activate_managed_window(id);
@@ -82,7 +92,9 @@ impl Ferese {
return;
}
if self.focus_cycle.is_none() {
let order = self.focus_history.candidates(self.focused_window, &available);
let order = self
.focus_history
.candidates(self.focused_window, &available);
if order.len() < 2 {
return;
}
@@ -194,7 +206,10 @@ mod tests {
}
fn placements(&self) -> Vec<Option<WindowPlacement>> {
self.ids.iter().map(|id| self.state.workspaces.placement(*id)).collect()
self.ids
.iter()
.map(|id| self.state.workspaces.placement(*id))
.collect()
}
fn press(&mut self, expected: usize) {
@@ -228,7 +243,10 @@ mod tests {
#[test]
fn tiled_focuses_most_recently_focused_float() {
let Some(mut f) = fixture("tiled_focuses_most_recently_focused_float", &[false, true, true]) else {
let Some(mut f) = fixture(
"tiled_focuses_most_recently_focused_float",
&[false, true, true],
) else {
return;
};
f.focus(1);
@@ -238,7 +256,10 @@ mod tests {
#[test]
fn floating_focuses_most_recently_focused_tile() {
let Some(mut f) = fixture("floating_focuses_most_recently_focused_tile", &[false, false, true]) else {
let Some(mut f) = fixture(
"floating_focuses_most_recently_focused_tile",
&[false, false, true],
) else {
return;
};
f.focus(0);
@@ -248,7 +269,10 @@ mod tests {
#[test]
fn multiple_floaters_do_not_cycle_or_wrap() {
let Some(mut f) = fixture("multiple_floaters_do_not_cycle_or_wrap", &[false, true, true, true]) else {
let Some(mut f) = fixture(
"multiple_floaters_do_not_cycle_or_wrap",
&[false, true, true, true],
) else {
return;
};
f.focus(2);
@@ -336,7 +360,10 @@ mod tests {
#[test]
fn focus_floating_preserves_all_window_placements() {
let Some(mut f) = fixture("focus_floating_preserves_all_window_placements", &[false, true, true]) else {
let Some(mut f) = fixture(
"focus_floating_preserves_all_window_placements",
&[false, true, true],
) else {
return;
};
let placements = f.placements();
@@ -351,7 +378,10 @@ mod tests {
use ferese_ipc::{Request, Response, VERSION, read_frame, write_frame};
use std::sync::mpsc;
use std::time::{Duration, Instant};
let Some(mut f) = fixture("ipc_focus_floating_uses_the_managed_activation_path", &[false, true]) else {
let Some(mut f) = fixture(
"ipc_focus_floating_uses_the_managed_activation_path",
&[false, true],
) else {
return;
};
f.focus(0);
@@ -361,7 +391,9 @@ mod tests {
let (send, receive) = mpsc::channel();
let worker = std::thread::spawn(move || {
let mut socket = UnixStream::connect(path).unwrap();
socket.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(3)))
.unwrap();
write_frame(
&mut socket,
&Request {
@@ -373,7 +405,8 @@ mod tests {
},
)
.unwrap();
send.send(read_frame::<Response>(&mut socket).unwrap()).unwrap();
send.send(read_frame::<Response>(&mut socket).unwrap())
.unwrap();
});
let deadline = Instant::now() + Duration::from_secs(3);
let response = loop {
@@ -381,7 +414,9 @@ mod tests {
break response;
}
assert!(Instant::now() < deadline, "IPC focus action timed out");
f.events.dispatch(Duration::from_millis(1), &mut f.state).unwrap();
f.events
.dispatch(Duration::from_millis(1), &mut f.state)
.unwrap();
};
worker.join().unwrap();
assert!(response.error.is_none(), "{:?}", response.error);
+38 -14
View File
@@ -1,7 +1,10 @@
use super::*;
impl Ferese {
pub fn surface_under(&self, position: Point<f64, Logical>) -> Option<(WlSurface, Point<f64, Logical>)> {
pub fn surface_under(
&self,
position: Point<f64, Logical>,
) -> Option<(WlSurface, Point<f64, Logical>)> {
if self.session_lock.active() {
return self.lock_surface_under(position);
}
@@ -29,7 +32,10 @@ impl Ferese {
})
}
pub fn layer_under(&self, position: Point<f64, Logical>) -> Option<(LayerSurface, WlSurface, Point<f64, Logical>)> {
pub fn layer_under(
&self,
position: Point<f64, Logical>,
) -> Option<(LayerSurface, WlSurface, Point<f64, Logical>)> {
if self.session_lock.active() {
return None;
}
@@ -69,7 +75,8 @@ impl Ferese {
return None;
}
let (source_x, source_y) = self.inverse_presented_window_point(*id, position.x, position.y)?;
let (source_x, source_y) =
self.inverse_presented_window_point(*id, position.x, position.y)?;
let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
let surface = window.surface_under(source_point, WindowSurfaceType::ALL);
@@ -110,7 +117,8 @@ impl Ferese {
for layer in map.layers_on(*requested).rev() {
let geometry = map.layer_geometry(layer)?;
let layer_position = output_position - geometry.loc.to_f64();
let Some((surface, surface_location)) = layer.surface_under(layer_position, WindowSurfaceType::ALL)
let Some((surface, surface_location)) =
layer.surface_under(layer_position, WindowSurfaceType::ALL)
else {
continue;
};
@@ -135,9 +143,13 @@ impl Ferese {
return;
}
send_frames_surface_tree(surface, output, self.start_time.elapsed(), None, |surface, _| {
eligible.get(&surface.into()).cloned()
});
send_frames_surface_tree(
surface,
output,
self.start_time.elapsed(),
None,
|surface, _| eligible.get(&surface.into()).cloned(),
);
}
pub fn window_under_visual(&self, position: Point<f64, Logical>) -> Option<Window> {
@@ -165,9 +177,13 @@ impl Ferese {
}
if !overview_active {
let (source_x, source_y) = self.inverse_presented_window_point(*id, position.x, position.y)?;
let source_point = Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
if window.surface_under(source_point, WindowSurfaceType::ALL).is_none()
let (source_x, source_y) =
self.inverse_presented_window_point(*id, position.x, position.y)?;
let source_point =
Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
if window
.surface_under(source_point, WindowSurfaceType::ALL)
.is_none()
&& !self.window_fills_visual_bounds(*id, workspace)
{
return None;
@@ -184,9 +200,13 @@ impl Ferese {
}
}
pub(super) fn workspace_under_pointer(&self, position: Point<f64, Logical>) -> Option<WorkspaceId> {
pub(super) fn workspace_under_pointer(
&self,
position: Point<f64, Logical>,
) -> Option<WorkspaceId> {
let output = self.space.output_under(position).next()?;
self.output_workspaces.active_workspace(self.output_id(output)?)
self.output_workspaces
.active_workspace(self.output_id(output)?)
}
pub(crate) fn window_belongs_to_output(&self, window: WindowId, output: &Output) -> bool {
@@ -194,7 +214,8 @@ impl Ferese {
return false;
};
if self.focus_cycle.is_some() && self.overview.is_active() {
return self.overview.has_window_preview(window) && self.focus_preview_output(window) == Some(output_id);
return self.overview.has_window_preview(window)
&& self.focus_preview_output(window) == Some(output_id);
}
let workspace = self.workspaces.workspace_for_window(window);
workspace == self.output_workspaces.active_workspace(output_id)
@@ -227,7 +248,10 @@ impl Ferese {
))
}
pub(crate) fn visual_rect_for_window(&self, window: &Window) -> Option<Rectangle<i32, Logical>> {
pub(crate) fn visual_rect_for_window(
&self,
window: &Window,
) -> Option<Rectangle<i32, Logical>> {
let id = self.windows.ids().get(window)?;
let rect = self.presented_window_rect(*id)?;
let size = ClientSize::from_rect(rect);
+146 -69
View File
@@ -51,7 +51,10 @@ impl Ferese {
return bounds;
};
let map = layer_map_for_output(output);
for layer in map.layers().filter(|layer| layer.namespace() == "ferese-shell-top-bar") {
for layer in map
.layers()
.filter(|layer| layer.namespace() == "ferese-shell-top-bar")
{
let policy = layer.cached_state();
let ExclusiveZone::Exclusive(amount) = policy.exclusive_zone else {
continue;
@@ -126,7 +129,13 @@ impl Ferese {
.tab_bars
.values()
.flatten()
.flat_map(|bar| bar.group.windows.iter().copied().filter(|id| *id != bar.group.active))
.flat_map(|bar| {
bar.group
.windows
.iter()
.copied()
.filter(|id| *id != bar.group.active)
})
.collect();
let windows = self
.windows
@@ -149,7 +158,8 @@ impl Ferese {
if inactive_tabs.contains(&id) {
// An inactive client must not hold its column's resize or viewport.
self.windows.clear_transaction(&id);
self.windows.update(id, |record| record.coupled_width = None);
self.windows
.update(id, |record| record.coupled_width = None);
}
self.space.unmap_elem(&window);
}
@@ -201,7 +211,8 @@ impl Ferese {
self.space
.outputs()
.filter(|output| {
self.output_has_animations(output) || self.output_has_pending_visual_changes(output)
self.output_has_animations(output)
|| self.output_has_pending_visual_changes(output)
})
.cloned(),
);
@@ -210,7 +221,8 @@ impl Ferese {
self.refresh_input_capture_zones();
let visible_workspaces = self.visible_workspace_ids();
self.reconcile_workspaces(&visible_workspaces);
self.tab_bars.retain(|id, _| visible_workspaces.contains(id));
self.tab_bars
.retain(|id, _| visible_workspaces.contains(id));
if self.session_lock.active() {
self.configure_lock_surfaces();
}
@@ -234,7 +246,9 @@ impl Ferese {
id,
record
.resize
.and_then(|transaction| transaction.column_width().map(|(source, _)| source))
.and_then(|transaction| {
transaction.column_width().map(|(source, _)| source)
})
.or_else(|| {
// A sibling may have committed while this column is still
// held. Replacement configures must share that cohort's
@@ -252,9 +266,13 @@ impl Ferese {
|| **sibling == column.windows[column.active]
})
.find_map(|sibling| {
self.windows.transaction(sibling).and_then(|transaction| {
transaction.column_width().map(|(source, _)| source)
})
self.windows.transaction(sibling).and_then(
|transaction| {
transaction
.column_width()
.map(|(source, _)| source)
},
)
})
})
})
@@ -271,7 +289,11 @@ impl Ferese {
continue;
};
for column in layout.columns() {
if !column.windows.iter().any(|id| pending_column_width_cycles.contains(id)) {
if !column
.windows
.iter()
.any(|id| pending_column_width_cycles.contains(id))
{
continue;
}
// A cycle belongs to the column. Copy one presented motion
@@ -280,10 +302,9 @@ impl Ferese {
let mut rows = column.windows.iter().filter(|id| {
(column.display != ferese_layout::ColumnDisplay::Tabbed
|| **id == column.windows[column.active])
&& self
.windows
.geometry(id)
.is_some_and(|geometry| !geometry.is_zooming() && !geometry.is_fullscreen())
&& self.windows.geometry(id).is_some_and(|geometry| {
!geometry.is_zooming() && !geometry.is_fullscreen()
})
});
let source = rows
.clone()
@@ -353,7 +374,9 @@ impl Ferese {
let workspace_focus = workspace.last_focused;
let focused = self
.focused_window
.filter(|window| self.workspaces.workspace_for_window(*window) == Some(workspace_id))
.filter(|window| {
self.workspaces.workspace_for_window(*window) == Some(workspace_id)
})
.or(workspace_focus);
let Some(workspace) = self.workspaces.workspace_mut(workspace_id) else {
continue;
@@ -403,12 +426,15 @@ impl Ferese {
crate::handlers::set_surface_tree_output(toplevel.wl_surface(), &output);
}
let is_maximized =
self.windows.record(*id).is_some_and(|w| w.maximized) && workspace_fullscreen != Some(*id);
let is_maximized = self.windows.record(*id).is_some_and(|w| w.maximized)
&& workspace_fullscreen != Some(*id);
let placement = self.workspaces.placement(*id);
let is_floating = matches!(placement, Some(WindowPlacement::Floating { .. }));
// Maximization cannot make an inactive tab visible again.
if !is_floating && workspace_fullscreen != Some(*id) && !layout.geometry.contains_key(id) {
if !is_floating
&& workspace_fullscreen != Some(*id)
&& !layout.geometry.contains_key(id)
{
continue;
}
let rect = if workspace_fullscreen == Some(*id) {
@@ -424,8 +450,10 @@ impl Ferese {
rect
}
Some(WindowPlacement::Floating { rect }) => {
let constrained =
constrained_floating_rect(rect, constraints.get(id).copied().unwrap_or_default());
let constrained = constrained_floating_rect(
rect,
constraints.get(id).copied().unwrap_or_default(),
);
if constrained != rect {
let _ = self.workspaces.set_floating_rect(*id, constrained);
}
@@ -438,13 +466,14 @@ impl Ferese {
let is_fullscreen = workspace_fullscreen == Some(*id);
visible.insert(*id);
let scrolling = if !is_fullscreen && !is_floating && (!is_maximized || is_scrolling_layout) {
viewport_target
.zip(viewport_current)
.map(|(target, current)| (workspace_id, rect.x + target, current))
} else {
None
};
let scrolling =
if !is_fullscreen && !is_floating && (!is_maximized || is_scrolling_layout) {
viewport_target
.zip(viewport_current)
.map(|(target, current)| (workspace_id, rect.x + target, current))
} else {
None
};
placements.push((
window.clone(),
@@ -465,7 +494,9 @@ impl Ferese {
placements.sort_by_key(|(_, id, ..)| self.window_stack.rank(*id));
for (window, id, rect, is_fullscreen, is_maximized, is_floating, scrolling, couple_width) in placements {
for (window, id, rect, is_fullscreen, is_maximized, is_floating, scrolling, couple_width) in
placements
{
let was_mapped = self.space.element_location(&window).is_some();
let Some(record) = self.windows.record_mut(id) else {
continue;
@@ -476,7 +507,8 @@ impl Ferese {
record.coupled_width = None;
}
let natural_pending = record.natural_floating_pending && is_floating && !is_fullscreen && !is_maximized;
let natural_pending =
record.natural_floating_pending && is_floating && !is_fullscreen && !is_maximized;
let had_geometry = record.geometry.is_some();
let geometry = record
.geometry
@@ -504,8 +536,12 @@ impl Ferese {
}
if let Some((workspace, world_x, viewport_x)) = scrolling {
let restored_world_x =
restored_scrolling_world_x(had_geometry, geometry.visual.current.x, viewport_x, world_x);
let restored_world_x = restored_scrolling_world_x(
had_geometry,
geometry.visual.current.x,
viewport_x,
world_x,
);
let mut animated_world_x = previous_scrolling_world_x
.remove(&id)
.filter(|(previous_workspace, _)| *previous_workspace == workspace)
@@ -534,19 +570,19 @@ impl Ferese {
}
record.world_x = Some((workspace, animated_world_x));
let coupled = !geometry.is_zooming()
&& (couple_width
|| record
.coupled_width
.as_ref()
.is_some_and(|(previous_workspace, _)| *previous_workspace == workspace));
let coupled =
!geometry.is_zooming()
&& (couple_width
|| record.coupled_width.as_ref().is_some_and(
|(previous_workspace, _)| *previous_workspace == workspace,
));
if coupled {
if let Some(source) = column_width_sources.get(&id) {
record.coupled_width = Some(*source);
}
let width = record
.coupled_width
.get_or_insert_with(|| (workspace, AnimatedValue::new(geometry.visual.current.width)));
let width = record.coupled_width.get_or_insert_with(|| {
(workspace, AnimatedValue::new(geometry.visual.current.width))
});
if width.0 != workspace {
*width = (workspace, AnimatedValue::new(geometry.visual.current.width));
}
@@ -628,7 +664,9 @@ impl Ferese {
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.and_then(|workspace| match &workspace.layout {
WorkspaceLayout::Scrolling(layout) => layout.allocated_column_width(id),
WorkspaceLayout::Scrolling(layout) => {
layout.allocated_column_width(id)
}
_ => None,
}),
),
@@ -670,7 +708,9 @@ impl Ferese {
pub(crate) fn sync_window_stacking(&mut self) {
let revision = self.window_stack.revision();
let rebuild = self.stacking_cache.needs_rebuild(revision, self.space.elements().len());
let rebuild = self
.stacking_cache
.needs_rebuild(revision, self.space.elements().len());
if !rebuild && !self.stacking_cache.needs_restore() {
return;
@@ -681,30 +721,43 @@ impl Ferese {
windows.sort_by_key(|window| {
let id = self.windows.ids().get(window).copied();
let geometry = id.and_then(|id| self.windows.geometry(&id));
let floating = id
.is_some_and(|id| matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. })));
let floating = id.is_some_and(|id| {
matches!(
self.workspaces.placement(id),
Some(WindowPlacement::Floating { .. })
)
});
let priority = crate::stacking::layer_priority(
floating,
geometry.is_some_and(|geometry| geometry.is_zooming()),
geometry.is_some_and(|geometry| geometry.is_fullscreen()),
floating
&& id.is_some_and(|id| {
self.floating_above_fullscreen.get(&id).is_some_and(|parent| {
self.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| workspace.fullscreen == Some(*parent))
})
self.floating_above_fullscreen
.get(&id)
.is_some_and(|parent| {
self.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.workspaces.workspace(workspace))
.is_some_and(|workspace| {
workspace.fullscreen == Some(*parent)
})
})
}),
);
(priority, id.map_or(usize::MAX, |id| self.window_stack.rank(id)))
(
priority,
id.map_or(usize::MAX, |id| self.window_stack.rank(id)),
)
});
self.stacking_cache.replace(windows, revision);
}
if !stacking_order_settled(self.space.elements(), self.stacking_cache.order(), |window| {
window.z_index()
}) {
if !stacking_order_settled(
self.space.elements(),
self.stacking_cache.order(),
|window| window.z_index(),
) {
for window in self.stacking_cache.order() {
self.space.raise_element(window, false);
}
@@ -722,16 +775,17 @@ impl Ferese {
self.stacking_cache.remapped();
}
pub(super) fn tiled_layout(&mut self, bounds: Rect) -> Result<LayoutResult, ferese_layout::LayoutError> {
pub(super) fn tiled_layout(
&mut self,
bounds: Rect,
) -> Result<LayoutResult, ferese_layout::LayoutError> {
let constraints = self.window_constraints();
let focused = self.focused_window;
let bounds = self.workspace_layout_bounds(bounds, &self.workspaces.active().layout);
self.workspaces.active_mut().layout.resolve_geometry_with_constraints(
bounds,
self.gap_config,
&constraints,
focused,
)
self.workspaces
.active_mut()
.layout
.resolve_geometry_with_constraints(bounds, self.gap_config, &constraints, focused)
}
#[cfg(test)]
@@ -745,7 +799,10 @@ impl Ferese {
let mut cache = self.workspace_previews.borrow_mut();
let mut live = HashSet::new();
let mut previews = HashMap::new();
for view in self.output_workspaces.workspace_views_iter(&self.workspaces) {
for view in self
.output_workspaces
.workspace_views_iter(&self.workspaces)
{
let Some(workspace) = self.workspaces.workspace(view.id) else {
continue;
};
@@ -799,19 +856,31 @@ impl Ferese {
// other shell metadata never invalidate geometry.
let mut layout = workspace.layout.clone();
let mut rectangles = layout
.resolve_geometry_with_constraints(input.bounds, input.gaps, &constraints, input.focused)
.resolve_geometry_with_constraints(
input.bounds,
input.gaps,
&constraints,
input.focused,
)
.map(|resolved| resolved.geometry)
.unwrap_or_default();
for window in &input.windows {
if let Some(rect) = window.floating {
rectangles.insert(window.id, constrained_floating_rect(rect, window.constraints));
rectangles.insert(
window.id,
constrained_floating_rect(rect, window.constraints),
);
}
if input.fullscreen == Some(window.id) {
rectangles.insert(window.id, input.fullscreen_bounds);
} else if window.maximized
&& (window.floating.is_some() || !matches!(workspace.layout, WorkspaceLayout::Scrolling(_)))
&& (window.floating.is_some()
|| !matches!(workspace.layout, WorkspaceLayout::Scrolling(_)))
{
rectangles.insert(window.id, maximized_rect(input.maximized_bounds, input.gaps.outer));
rectangles.insert(
window.id,
maximized_rect(input.maximized_bounds, input.gaps.outer),
);
}
}
cache.entries.insert(
@@ -827,7 +896,12 @@ impl Ferese {
cache.resolutions += 1;
}
}
previews.extend(cache.entries[&view.id].rectangles.iter().map(|(&id, &rect)| (id, rect)));
previews.extend(
cache.entries[&view.id]
.rectangles
.iter()
.map(|(&id, &rect)| (id, rect)),
);
}
cache.entries.retain(|id, _| live.contains(id));
previews
@@ -844,8 +918,11 @@ impl Ferese {
let state = cached.current();
(state.min_size, state.max_size)
});
let (minimum, maximum) =
self.effective_size_constraints(window, (minimum.w, minimum.h), (maximum.w, maximum.h));
let (minimum, maximum) = self.effective_size_constraints(
window,
(minimum.w, minimum.h),
(maximum.w, maximum.h),
);
Some((
*id,
+28 -6
View File
@@ -7,7 +7,11 @@ impl Ferese {
// on_commit_buffer_handler has not reset the previous renderer state.
let removed = with_states(surface, |states| {
matches!(
states.cached_state.get::<SurfaceAttributes>().current().buffer,
states
.cached_state
.get::<SurfaceAttributes>()
.current()
.buffer,
Some(BufferAssignment::Removed)
)
});
@@ -23,13 +27,20 @@ impl Ferese {
}
pub(super) fn retain_closed_window(&mut self, window: &Window) {
if !self.animations_enabled || self.session_lock.active() || self.render.closing.len() >= 64 {
if !self.animations_enabled || self.session_lock.active() || self.render.closing.len() >= 64
{
return;
}
let Some(&id) = self.windows.ids().get(window) else {
return;
};
if self.render.closing.iter().any(|window| window.presentation.id == id) || !self.window_content_ready(window) {
if self
.render
.closing
.iter()
.any(|window| window.presentation.id == id)
|| !self.window_content_ready(window)
{
return;
}
let Some(output) = self
@@ -77,8 +88,17 @@ impl Ferese {
.position(|candidate| *candidate == id)
.and_then(|index| order.get(index + 1))
.copied();
let used = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum::<usize>()
+ self.render.closing.iter().map(|window| window.bytes()).sum::<usize>();
let used = self
.render
.snapshots()
.map(|snapshot| snapshot.bytes())
.sum::<usize>()
+ self
.render
.closing
.iter()
.map(|window| window.bytes())
.sum::<usize>();
let remaining = crate::presentation::SNAPSHOT_BUDGET.saturating_sub(used);
let scale = output.current_scale().fractional_scale();
let result = if let Some(backend) = &self.nested_backend {
@@ -103,7 +123,9 @@ impl Ferese {
let handoff = self
.render
.snapshot(&id)
.filter(|old| old.context == snapshot.context && (old.scale - scale).abs() < 0.001)
.filter(|old| {
old.context == snapshot.context && (old.scale - scale).abs() < 0.001
})
.cloned()
.map(|old| (old, handoff_size));
let material = window.toplevel().and_then(|toplevel| {
+138 -63
View File
@@ -11,11 +11,14 @@ impl Ferese {
&& !self.overview.is_presenting()
&& self.workspaces.active_id() == swipe.workspace
&& self.focused_window == Some(swipe.from)
&& self.workspaces.workspace(swipe.workspace).is_some_and(|workspace| {
workspace.fullscreen.is_none()
&& workspace.layout.contains(swipe.from)
&& workspace.layout.contains(swipe.to)
})
&& self
.workspaces
.workspace(swipe.workspace)
.is_some_and(|workspace| {
workspace.fullscreen.is_none()
&& workspace.layout.contains(swipe.from)
&& workspace.layout.contains(swipe.to)
})
}
pub(crate) fn preview_focus_swipe(
@@ -25,16 +28,28 @@ impl Ferese {
progress: f64,
) {
if self.focus_swipe().is_none() {
if self.swipe.preview_started() || !self.animations_enabled || self.overview.is_presenting() {
if self.swipe.preview_started()
|| !self.animations_enabled
|| self.overview.is_presenting()
{
return;
}
let Some(from) = self.focused_window else { return };
let Some(bounds) = self.output_bounds() else { return };
let Some(from) = self.focused_window else {
return;
};
let Some(bounds) = self.output_bounds() else {
return;
};
let workspace = self.workspaces.active();
if workspace.fullscreen.is_some() || !matches!(workspace.layout, WorkspaceLayout::Scrolling(_)) {
if workspace.fullscreen.is_some()
|| !matches!(workspace.layout, WorkspaceLayout::Scrolling(_))
{
return;
}
let Ok(neighbor) = workspace.layout.directional_neighbor(from, direction, bounds) else {
let Ok(neighbor) = workspace
.layout
.directional_neighbor(from, direction, bounds)
else {
return;
};
let to = neighbor.unwrap_or(from);
@@ -54,7 +69,12 @@ impl Ferese {
}
let layout_bounds = self.workspace_layout_bounds(bounds, &candidate);
if candidate
.resolve_geometry_with_constraints(layout_bounds, self.gap_config, &self.window_constraints(), Some(to))
.resolve_geometry_with_constraints(
layout_bounds,
self.gap_config,
&self.window_constraints(),
Some(to),
)
.is_err()
{
return;
@@ -86,7 +106,10 @@ impl Ferese {
self.swipe.mark_preview_started();
}
if let Some(workspace) = self.focus_swipe().map(|swipe| swipe.workspace) {
self.viewports.get_mut(&workspace).unwrap().update_gesture(progress);
self.viewports
.get_mut(&workspace)
.unwrap()
.update_gesture(progress);
}
if let Some(output) = self
@@ -109,8 +132,10 @@ impl Ferese {
.viewport_blocked(swipe.workspace, &self.windows);
// Release from the last input position even if no frame rendered that update.
let velocity = (current && !blocked).then(|| {
swipe.release_velocity(self.swipe.release_velocity, self.swipe.unbounded_release_velocity)
/ self.animation_speed
swipe.release_velocity(
self.swipe.release_velocity,
self.swipe.unbounded_release_velocity,
) / self.animation_speed
});
let workspace = swipe.workspace;
let swipe = self
@@ -178,17 +203,23 @@ impl Ferese {
let Some(window) = self.windows.window(next).cloned() else {
return;
};
let Some(surface) = window.toplevel().map(|toplevel| toplevel.wl_surface().clone()) else {
let Some(surface) = window
.toplevel()
.map(|toplevel| toplevel.wl_surface().clone())
else {
return;
};
self.focused_window = Some(next);
self.raise_window(&window, true);
self.seat.get_keyboard().expect("seat has a keyboard").set_focus(
self,
Some(surface),
smithay::utils::SERIAL_COUNTER.next_serial(),
);
self.seat
.get_keyboard()
.expect("seat has a keyboard")
.set_focus(
self,
Some(surface),
smithay::utils::SERIAL_COUNTER.next_serial(),
);
for window in self.space.elements() {
if let Some(toplevel) = window.toplevel() {
@@ -281,18 +312,22 @@ impl Ferese {
return;
};
if self.workspaces.active().layout.mode() == LayoutMode::Tree {
if let Some(direction) = [Direction::Left, Direction::Right, Direction::Up, Direction::Down]
.into_iter()
.find(|direction| {
self.workspaces
.active()
.layout
.directional_neighbor(window, *direction, bounds)
.ok()
.flatten()
.is_some()
})
{
if let Some(direction) = [
Direction::Left,
Direction::Right,
Direction::Up,
Direction::Down,
]
.into_iter()
.find(|direction| {
self.workspaces
.active()
.layout
.directional_neighbor(window, *direction, bounds)
.ok()
.flatten()
.is_some()
}) {
self.join_tab(direction);
}
return;
@@ -343,9 +378,13 @@ impl Ferese {
return;
};
match self.workspaces.cycle_column_width(window, &self.column_width_presets) {
match self
.workspaces
.cycle_column_width(window, &self.column_width_presets)
{
Ok(true) => {
self.windows.update(window, |w| w.column_width_pending = true);
self.windows
.update(window, |w| w.column_width_pending = true);
self.relayout();
}
Ok(false) => {}
@@ -463,22 +502,26 @@ impl Ferese {
let Some(output) = self.output_workspaces.focused_output() else {
return;
};
let Some(workspace) = self.output_workspaces.workspace_at(&self.workspaces, output, index) else {
let Some(workspace) = self
.output_workspaces
.workspace_at(&self.workspaces, output, index)
else {
return;
};
let previous = self.output_workspaces.active_workspace(output);
let owner = match self
.output_workspaces
.select_workspace(output, workspace, auto_back_and_forth)
{
Ok(ferese_core::WorkspaceSwitch::Activated(output))
| Ok(ferese_core::WorkspaceSwitch::FocusedExisting(output)) => output,
Err(error) => {
tracing::error!(%error, index, "failed to assign workspace to output");
return;
}
};
let owner =
match self
.output_workspaces
.select_workspace(output, workspace, auto_back_and_forth)
{
Ok(ferese_core::WorkspaceSwitch::Activated(output))
| Ok(ferese_core::WorkspaceSwitch::FocusedExisting(output)) => output,
Err(error) => {
tracing::error!(%error, index, "failed to assign workspace to output");
return;
}
};
let workspace = self
.output_workspaces
.active_workspace(owner)
@@ -490,7 +533,12 @@ impl Ferese {
self.restore_keyboard_focus();
}
pub(crate) fn preview_workspace_swipe(&mut self, next: bool, direction: SwipeDirection, progress: f64) {
pub(crate) fn preview_workspace_swipe(
&mut self,
next: bool,
direction: SwipeDirection,
progress: f64,
) {
if !self.animations_enabled || self.overview.is_presenting() {
return;
}
@@ -554,7 +602,10 @@ impl Ferese {
&& self.activate_managed_workspace_from_swipe(to, expected);
if !committed {
if let Some(slide) = self.workspace_slides.get_mut(&output) {
slide.release_with_velocity(false, self.swipe.release_velocity / self.animation_speed);
slide.release_with_velocity(
false,
self.swipe.release_velocity / self.animation_speed,
);
}
self.last_animation_tick = Instant::now();
self.relayout();
@@ -562,7 +613,10 @@ impl Ferese {
true
}
pub(crate) fn workspace_slide_workspaces(&self, output: OutputId) -> impl Iterator<Item = WorkspaceId> + '_ {
pub(crate) fn workspace_slide_workspaces(
&self,
output: OutputId,
) -> impl Iterator<Item = WorkspaceId> + '_ {
self.workspace_slides
.get(&output)
.into_iter()
@@ -589,9 +643,9 @@ impl Ferese {
}
if owner == requested_output
&& let Some(slide) = previous_slide.as_mut()
&& slide
.gesture
.is_some_and(|(_, to, direction)| to == workspace && Some(direction) == slide_direction)
&& slide.gesture.is_some_and(|(_, to, direction)| {
to == workspace && Some(direction) == slide_direction
})
{
slide.release_with_velocity(true, self.swipe.release_velocity / self.animation_speed);
self.last_animation_tick = Instant::now();
@@ -640,8 +694,18 @@ impl Ferese {
}
}
pub(crate) fn move_managed_window_to_workspace(&mut self, window: WindowId, destination: WorkspaceId) -> bool {
if self.session_lock.active() || self.windows.window(window).and_then(|w| w.toplevel()).is_none() {
pub(crate) fn move_managed_window_to_workspace(
&mut self,
window: WindowId,
destination: WorkspaceId,
) -> bool {
if self.session_lock.active()
|| self
.windows
.window(window)
.and_then(|w| w.toplevel())
.is_none()
{
return false;
}
let Some(source) = self.workspaces.workspace_for_window(window) else {
@@ -661,7 +725,10 @@ impl Ferese {
let axis = bounds
.and_then(|bounds| {
let workspace = self.workspaces.workspace(destination)?;
workspace.layout.automatic_axis(workspace.last_focused, bounds).ok()
workspace
.layout
.automatic_axis(workspace.last_focused, bounds)
.ok()
})
.unwrap_or(Axis::Horizontal);
if self
@@ -686,18 +753,27 @@ impl Ferese {
let Some(output) = self.output_workspaces.focused_output() else {
return;
};
let Some(destination) = self.output_workspaces.workspace_at(&self.workspaces, output, index) else {
let Some(destination) =
self.output_workspaces
.workspace_at(&self.workspaces, output, index)
else {
return;
};
let axis = self
.output_bounds()
.and_then(|bounds| {
let target = self.workspaces.workspace(destination)?;
target.layout.automatic_axis(target.last_focused, bounds).ok()
target
.layout
.automatic_axis(target.last_focused, bounds)
.ok()
})
.unwrap_or(Axis::Horizontal);
if let Err(error) = self.workspaces.move_window_to_workspace(window, destination, axis, 0.5) {
if let Err(error) = self
.workspaces
.move_window_to_workspace(window, destination, axis, 0.5)
{
tracing::error!(%error, ?window, index, "failed to move window to workspace");
return;
}
@@ -725,10 +801,9 @@ impl Ferese {
.map(|toplevel| toplevel.wl_surface().clone())
});
self.seat.get_keyboard().expect("seat has a keyboard").set_focus(
self,
surface,
smithay::utils::SERIAL_COUNTER.next_serial(),
);
self.seat
.get_keyboard()
.expect("seat has a keyboard")
.set_focus(self, surface, smithay::utils::SERIAL_COUNTER.next_serial());
}
}
+38 -13
View File
@@ -18,11 +18,17 @@ impl Ferese {
}
pub(crate) fn ensure_output_identity(&mut self, identity: &str) -> OutputId {
*self.output_identity_ids.entry(identity.to_owned()).or_insert_with(|| {
let id = OutputId(self.next_output_id);
self.next_output_id = self.next_output_id.checked_add(1).expect("output ID space exhausted");
id
})
*self
.output_identity_ids
.entry(identity.to_owned())
.or_insert_with(|| {
let id = OutputId(self.next_output_id);
self.next_output_id = self
.next_output_id
.checked_add(1)
.expect("output ID space exhausted");
id
})
}
pub fn register_output(&mut self, output: &Output, identity: String) {
@@ -86,7 +92,9 @@ impl Ferese {
.into_iter()
.filter(|entry| entry.output != *output)
.collect();
let changes = self.publish_desktop(runtime).expect("valid surviving output inventory");
let changes = self
.publish_desktop(runtime)
.expect("valid surviving output inventory");
self.finish_desktop_transition(changes);
}
}
@@ -102,12 +110,15 @@ impl Ferese {
.outputs()
.find(|candidate| *candidate != output)
.map(|output| output.name());
self.activities.store.remove_output(&output.name(), fallback.as_deref());
self.activities
.store
.remove_output(&output.name(), fallback.as_deref());
self.activities
.pomodoro_remove_output(&output.name(), fallback.as_deref());
self.activities.changed();
self.display_presentation.remove_output(output);
self.output_redraw_pending.retain(|pending| pending != output);
self.output_redraw_pending
.retain(|pending| pending != output);
if let Some(id) = self.output_ids.get(output) {
self.render.remove_output(*id);
self.capture_render.remove_output(*id);
@@ -123,7 +134,11 @@ impl Ferese {
self.session_lock.output_removed(output);
}
pub(super) fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
pub(super) fn activate_output_workspace(
&mut self,
output: OutputId,
workspace: ferese_core::WorkspaceId,
) {
if let Err(error) = self.output_workspaces.focus_output(output) {
tracing::error!(%error, "failed to focus output");
return;
@@ -163,7 +178,9 @@ impl Ferese {
let Some(workspace) = self.output_workspaces.active_workspace(output_id) else {
return;
};
if self.output_workspaces.focused_output() == Some(output_id) && self.workspaces.active_id() == workspace {
if self.output_workspaces.focused_output() == Some(output_id)
&& self.workspaces.active_id() == workspace
{
return;
}
@@ -176,7 +193,9 @@ impl Ferese {
mod tests {
use super::*;
use smithay::backend::input::{ButtonState, Event, InputBackend, InputEvent, PointerButtonEvent, UnusedEvent};
use smithay::backend::input::{
ButtonState, Event, InputBackend, InputEvent, PointerButtonEvent, UnusedEvent,
};
use smithay::backend::winit::WinitVirtualDevice;
use smithay::input::pointer::MotionEvent;
use smithay::utils::SERIAL_COUNTER;
@@ -316,7 +335,10 @@ mod tests {
card.rect.x + card.rect.width / 2.0,
card.rect.y + card.rect.height / 2.0,
));
assert_eq!(state.overview_strip_at(position), Some(expected_output.clone()));
assert_eq!(
state.overview_strip_at(position),
Some(expected_output.clone())
);
click(&mut state, position);
assert_eq!(state.workspaces.active_id(), card.workspace);
state.windows.records.insert(
@@ -332,7 +354,10 @@ mod tests {
.find(|card| card.workspace == expected_workspace)
.unwrap();
let preview = occupied.windows[0].1;
let image_point = Point::from((preview.x + preview.width / 2.0, preview.y + preview.height / 2.0));
let image_point = Point::from((
preview.x + preview.width / 2.0,
preview.y + preview.height / 2.0,
));
let label_point = Point::from((
occupied.rect.x + occupied.rect.width / 2.0,
occupied.rect.y + occupied.rect.height - 8.0,
+10 -3
View File
@@ -100,14 +100,19 @@ fn every_panel_style_owns_clearance_at_its_reserved_edge() {
request(&mut wire, 3, 2, &[panel.exclusive_zone(false) as u32]);
request(&mut wire, 3, 3, &[geometry.edge_margin as u32, 0, 0, 0]);
request(&mut wire, 2, 6, &[]);
events.dispatch(Duration::from_millis(1), &mut state).unwrap();
events
.dispatch(Duration::from_millis(1), &mut state)
.unwrap();
let bounds = state.output_bounds_for(&output).unwrap();
let edge = f64::from(geometry.edge_margin + 36 + clearance);
assert_eq!(bounds.y, edge, "{style:?}, clearance={clearance}");
for outer in [0., 12.] {
let adjusted = state.panel_layout_bounds(bounds, outer);
let window = maximized_rect(adjusted, outer);
assert_eq!(window.y, edge, "{style:?}, clearance={clearance}, outer={outer}");
assert_eq!(
window.y, edge,
"{style:?}, clearance={clearance}, outer={outer}"
);
assert_eq!(window.x, bounds.x + outer);
assert_eq!(window.y + window.height, bounds.y + bounds.height - outer);
}
@@ -115,7 +120,9 @@ fn every_panel_style_owns_clearance_at_its_reserved_edge() {
}
request(&mut wire, 3, 2, &[0]);
request(&mut wire, 2, 6, &[]);
events.dispatch(Duration::from_millis(1), &mut state).unwrap();
events
.dispatch(Duration::from_millis(1), &mut state)
.unwrap();
let bounds = state.output_bounds_for(&output).unwrap();
assert_eq!(
state.panel_layout_bounds(bounds, 12.),
+220 -96
View File
@@ -16,7 +16,10 @@ pub(crate) struct WindowFrame {
}
impl Ferese {
pub(crate) fn current_window_presentation(&self, id: WindowId) -> Option<crate::presentation::WindowPresentation> {
pub(crate) fn current_window_presentation(
&self,
id: WindowId,
) -> Option<crate::presentation::WindowPresentation> {
let geometry = *self.windows.geometry(&id)?;
Some(self.compose_window_presentation(
id,
@@ -27,7 +30,12 @@ impl Ferese {
))
}
fn window_focus_effect(&self, id: WindowId, delta: Duration, overview_opacity: f32) -> (f32, f64) {
fn window_focus_effect(
&self,
id: WindowId,
delta: Duration,
overview_opacity: f32,
) -> (f32, f64) {
let target = crate::focus_effect::target(self.focus_effect, self.focused_window, id);
let mut focus = self
.windows
@@ -43,8 +51,11 @@ impl Ferese {
0.0
},
);
self.focus_effect
.resolve(focus.current, self.focused_window.is_some(), overview_opacity)
self.focus_effect.resolve(
focus.current,
self.focused_window.is_some(),
overview_opacity,
)
}
fn compose_window_presentation(
@@ -55,9 +66,15 @@ impl Ferese {
offset: (f64, f64),
delta: Duration,
) -> crate::presentation::WindowPresentation {
let record = self.windows.record(id).expect("presentation has a window record");
let record = self
.windows
.record(id)
.expect("presentation has a window record");
let mut visual = geometry.visual;
if self.presentation_dependencies.reflow_blocked(id, &self.windows) {
if self
.presentation_dependencies
.reflow_blocked(id, &self.windows)
{
let predicted_x_velocity = visual.velocity.x;
visual.velocity = Default::default();
if !geometry.is_zooming()
@@ -115,12 +132,18 @@ impl Ferese {
..self.spring_config
},
);
emphasis.advance(delta, crate::presentation::emphasis_spring(self.spring_config));
shadow.advance(delta, crate::presentation::shadow_spring(self.spring_config));
emphasis.advance(
delta,
crate::presentation::emphasis_spring(self.spring_config),
);
shadow.advance(
delta,
crate::presentation::shadow_spring(self.spring_config),
);
}
let presence_scale = 0.97 + 0.03 * opacity.current;
let native_size =
(!overview.is_presenting() && presence_scale != 1.0).then(|| ClientSize::from_rect(bounds.current));
let native_size = (!overview.is_presenting() && presence_scale != 1.0)
.then(|| ClientSize::from_rect(bounds.current));
bounds.velocity = crate::presentation::scaled_visual_velocity(
bounds.current,
bounds.velocity,
@@ -161,7 +184,10 @@ impl Ferese {
Some((
id,
self.windows.record(id)?,
block_resizes && self.presentation_dependencies.reflow_blocked(id, &self.windows),
block_resizes
&& self
.presentation_dependencies
.reflow_blocked(id, &self.windows),
))
})
})
@@ -173,20 +199,21 @@ impl Ferese {
} else {
self.focused_window
};
self.output_window_records(output, false).any(|(id, record, _)| {
self.window_belongs_to_output(id, output)
&& (record
.focus
.as_ref()
.is_some_and(|focus| focus.needs_update(if selected == Some(id) { 1.0 } else { 0.0 }))
|| record
.shadow
.as_ref()
.is_some_and(|shadow| shadow.needs_update(if selected == Some(id) { 1.0 } else { 0.0 }))
|| record.focus_transition.as_ref().is_some_and(|transition| {
transition.needs_update(crate::focus_effect::target(self.focus_effect, self.focused_window, id))
self.output_window_records(output, false)
.any(|(id, record, _)| {
self.window_belongs_to_output(id, output)
&& (record.focus.as_ref().is_some_and(|focus| {
focus.needs_update(if selected == Some(id) { 1.0 } else { 0.0 })
}) || record.shadow.as_ref().is_some_and(|shadow| {
shadow.needs_update(if selected == Some(id) { 1.0 } else { 0.0 })
}) || record.focus_transition.as_ref().is_some_and(|transition| {
transition.needs_update(crate::focus_effect::target(
self.focus_effect,
self.focused_window,
id,
))
}))
})
})
}
pub(crate) fn output_resize_deadline(&self, output: &Output) -> Option<Duration> {
@@ -212,7 +239,8 @@ impl Ferese {
}
fn snapshot_is_waiting(&self, id: WindowId, geometry: &WindowGeometry) -> bool {
self.presentation_dependencies.reflow_blocked(id, &self.windows)
self.presentation_dependencies
.reflow_blocked(id, &self.windows)
|| geometry.client.committed_size.is_some_and(|size| {
crate::presentation::resize_needs_old_frame(
geometry.visual.current,
@@ -247,48 +275,58 @@ impl Ferese {
return true;
}
self.output_window_records(output, true).any(|(id, record, blocked)| {
let Some(mut geometry) = record.geometry else {
return false;
};
self.output_window_records(output, true)
.any(|(id, record, blocked)| {
let Some(mut geometry) = record.geometry else {
return false;
};
if !self.window_belongs_to_output(id, output) {
return false;
}
if !self.window_belongs_to_output(id, output) {
return false;
}
if record.opening.is_some()
|| (self.render.snapshot(&id).is_some() && !self.snapshot_is_waiting(id, &geometry))
|| record.focus.as_ref().is_some_and(|focus| focus.is_animating())
|| record.shadow.as_ref().is_some_and(|shadow| shadow.is_animating())
|| record
.focus_transition
.as_ref()
.is_some_and(|transition| transition.is_animating())
{
return true;
}
if record.opening.is_some()
|| (self.render.snapshot(&id).is_some()
&& !self.snapshot_is_waiting(id, &geometry))
|| record
.focus
.as_ref()
.is_some_and(|focus| focus.is_animating())
|| record
.shadow
.as_ref()
.is_some_and(|shadow| shadow.is_animating())
|| record
.focus_transition
.as_ref()
.is_some_and(|transition| transition.is_animating())
{
return true;
}
if !blocked && geometry.advance(Duration::ZERO, self.spring_config, self.animations_enabled) {
return true;
}
if !blocked
&& geometry.advance(Duration::ZERO, self.spring_config, self.animations_enabled)
{
return true;
}
let Some(workspace) = self.workspaces.workspace_for_window(id) else {
return false;
};
let Some(workspace) = self.workspaces.workspace_for_window(id) else {
return false;
};
(!self
.presentation_dependencies
.viewport_blocked(workspace, &self.windows)
&& self
.viewports
.get(&workspace)
.is_some_and(ViewportPresentation::needs_tick))
|| (!blocked
&& record.coupled_width.as_ref().is_some_and(|(_, width)| {
let mut width = *width;
width.advance(Duration::ZERO, self.viewport_spring_config)
}))
})
(!self
.presentation_dependencies
.viewport_blocked(workspace, &self.windows)
&& self
.viewports
.get(&workspace)
.is_some_and(ViewportPresentation::needs_tick))
|| (!blocked
&& record.coupled_width.as_ref().is_some_and(|(_, width)| {
let mut width = *width;
width.advance(Duration::ZERO, self.viewport_spring_config)
}))
})
}
pub(crate) fn sample_frame(&self, output: &Output, horizon: Duration) -> FrameScene {
@@ -300,15 +338,17 @@ impl Ferese {
};
let overview = self.overview.sample(delta, self.spring_config);
let predicted_slide_offsets =
(!delta.is_zero()).then(|| self.sample_workspace_slide_offsets(self.output_id(output), delta));
let predicted_slide_offsets = (!delta.is_zero())
.then(|| self.sample_workspace_slide_offsets(self.output_id(output), delta));
let slide_offsets = predicted_slide_offsets
.as_ref()
.unwrap_or(&self.workspace_slide_offsets);
let mut windows = HashMap::new();
for (id, record, blocked) in self.output_window_records(output, !delta.is_zero()) {
let Some(original) = record.geometry else { continue };
let Some(original) = record.geometry else {
continue;
};
// The workspace strip also draws inactive workspaces on this output.
// Keep their samples through the overview exit transition; the main
@@ -350,8 +390,15 @@ impl Ferese {
.workspace_for_window(id)
.and_then(|workspace| slide_offsets.get(&workspace).copied())
.unwrap_or_default();
let presentation = self.compose_window_presentation(id, geometry, &overview, offset, delta);
windows.insert(id, WindowFrame { geometry, presentation });
let presentation =
self.compose_window_presentation(id, geometry, &overview, offset, delta);
windows.insert(
id,
WindowFrame {
geometry,
presentation,
},
);
}
FrameScene {
@@ -446,7 +493,10 @@ mod tests {
state.space.map_output(&output, (0, 0));
state.register_output(&output, "resize-idle".into());
let id = WindowId(1);
state.workspaces.insert_window(id, Axis::Horizontal, 0.5).unwrap();
state
.workspaces
.insert_window(id, Axis::Horizontal, 0.5)
.unwrap();
let settled = WindowGeometry::new(Rect::new(0., 0., 400., 300.), None);
state.windows.records.insert(
id,
@@ -460,7 +510,10 @@ mod tests {
crate::resize_transaction::ResizeTransaction::new(9.into(), Duration::ZERO),
);
state.rebuild_presentation_dependencies();
assert_eq!(state.output_resize_deadline(&output), Some(Duration::from_millis(300)));
assert_eq!(
state.output_resize_deadline(&output),
Some(Duration::from_millis(300))
);
let mut held = settled;
held.visual.set_target(Rect::new(100., 0., 400., 300.));
state.windows.set_geometry(id, held);
@@ -500,13 +553,18 @@ mod tests {
state.release_presentation_dependencies();
assert_eq!(state.output_resize_deadline(&output), None);
assert!(state.output_has_animations(&output));
state.windows.geometry_mut(&id).unwrap().client.request_size(
ClientSize {
width: 800,
height: 300,
},
Duration::ZERO,
);
state
.windows
.geometry_mut(&id)
.unwrap()
.client
.request_size(
ClientSize {
width: 800,
height: 300,
},
Duration::ZERO,
);
assert_eq!(
state.output_resize_deadline(&output),
None,
@@ -594,14 +652,26 @@ mod tests {
let first = WindowId(1);
let second = WindowId(2);
state.focused_window = Some(first);
assert_eq!(state.current_window_presentation(first).unwrap().alpha(), 0.9);
assert_eq!(state.current_window_presentation(second).unwrap().alpha(), 0.6);
assert_eq!(
state.current_window_presentation(first).unwrap().alpha(),
0.9
);
assert_eq!(
state.current_window_presentation(second).unwrap().alpha(),
0.6
);
// Only emphasis follows overview selection; activation and its fade remain unchanged.
state.set_overview_active(true);
state.overview.select_window(second);
assert_eq!(state.focused_window, Some(first));
assert_eq!(state.current_window_presentation(first).unwrap().alpha(), 0.9);
assert_eq!(state.current_window_presentation(second).unwrap().alpha(), 0.6);
assert_eq!(
state.current_window_presentation(first).unwrap().alpha(),
0.9
);
assert_eq!(
state.current_window_presentation(second).unwrap().alpha(),
0.6
);
state.set_overview_active(false);
state.focused_window = Some(second);
let transition = state
@@ -640,8 +710,14 @@ mod tests {
);
assert!(state.windows.transaction(&first).is_some());
state.animations_enabled = false;
assert_eq!(state.current_window_presentation(first).unwrap().alpha(), 0.6);
assert_eq!(state.current_window_presentation(second).unwrap().alpha(), 0.9);
assert_eq!(
state.current_window_presentation(first).unwrap().alpha(),
0.6
);
assert_eq!(
state.current_window_presentation(second).unwrap().alpha(),
0.9
);
}
#[test]
@@ -680,7 +756,10 @@ mod tests {
let output_id = state.output_id(&output).unwrap();
let from = state.output_workspaces.active_workspace(output_id).unwrap();
let to = state.workspaces.create_workspace();
state.output_workspaces.assign_workspace(output_id, to).unwrap();
state
.output_workspaces
.assign_workspace(output_id, to)
.unwrap();
for (id, workspace) in [(WindowId(1), from), (WindowId(2), to)] {
let normal = Rect::new(100., 160., 640., 480.);
state
@@ -697,7 +776,11 @@ mod tests {
}
state.set_overview_active(true);
state.advance_animations_by(Duration::from_secs(10));
let old = state.current_window_presentation(WindowId(1)).unwrap().bounds.current;
let old = state
.current_window_presentation(WindowId(1))
.unwrap()
.bounds
.current;
let cards = state.overview_workspace_cards(&output);
let card = cards.iter().find(|card| card.workspace == to).unwrap();
assert!(
@@ -711,15 +794,37 @@ mod tests {
);
assert!(state.workspace_slides.contains_key(&output_id));
assert!(state.window_belongs_to_output(WindowId(1), &output));
let incoming = state.current_window_presentation(WindowId(2)).unwrap().bounds.current;
let incoming = state
.current_window_presentation(WindowId(2))
.unwrap()
.bounds
.current;
assert_eq!(incoming.width, old.width);
assert_eq!(incoming.height, old.height);
assert!(incoming.x > old.x + 1000.);
let predicted = state.sample_frame(&output, Duration::from_millis(60));
assert!(predicted.windows[&WindowId(1)].presentation.bounds.current.x < old.x);
assert!(predicted.windows[&WindowId(2)].presentation.bounds.current.x < incoming.x);
assert!(
predicted.windows[&WindowId(1)]
.presentation
.bounds
.current
.x
< old.x
);
assert!(
predicted.windows[&WindowId(2)]
.presentation
.bounds
.current
.x
< incoming.x
);
assert_eq!(
predicted.windows[&WindowId(2)].presentation.bounds.current.width,
predicted.windows[&WindowId(2)]
.presentation
.bounds
.current
.width,
incoming.width
);
// Activation can add a trailing empty workspace. Once that layout is
@@ -745,7 +850,10 @@ mod tests {
assert_eq!(item.velocity, after.velocity);
}
// Finish on the selected workspace and retire all outgoing previews.
state.output_workspaces.switch_workspace(output_id, from).unwrap();
state
.output_workspaces
.switch_workspace(output_id, from)
.unwrap();
state.retarget_overview();
state.advance_animations_by(Duration::from_secs(10));
assert!(state.workspace_slides.is_empty());
@@ -802,7 +910,10 @@ mod tests {
state.space.map_output(&output, (0, 0));
state.register_output(&output, "forecast-test".into());
let id = WindowId(1);
state.workspaces.insert_window(id, Axis::Horizontal, 0.5).unwrap();
state
.workspaces
.insert_window(id, Axis::Horizontal, 0.5)
.unwrap();
let mut geometry = WindowGeometry::new(Rect::new(0., 0., 400., 300.), None);
geometry.visual.set_target(Rect::new(600., 0., 400., 300.));
// This test isolates frame sampling from the protocol client fixture.
@@ -821,7 +932,9 @@ mod tests {
] {
{
let forecast = state.sample_frame(&output, horizon);
assert!(forecast.windows[&id].geometry.visual.current.x > geometry.visual.current.x);
assert!(
forecast.windows[&id].geometry.visual.current.x > geometry.visual.current.x
);
assert!(forecast.animating);
}
@@ -898,7 +1011,10 @@ mod tests {
let output_id = state.output_id(&output).unwrap();
let from = state.workspaces.workspace_for_window(id).unwrap();
let to = state.workspaces.create_workspace();
state.output_workspaces.assign_workspace(output_id, to).unwrap();
state
.output_workspaces
.assign_workspace(output_id, to)
.unwrap();
let mut slide = WorkspaceSlide::new(None, from, to, SwipeDirection::Left);
slide.held_progress = Some(0.25);
state.workspace_slides.insert(output_id, slide);
@@ -907,12 +1023,16 @@ mod tests {
assert_eq!(offsets[&from].0, -480.0);
assert_eq!(offsets[&to].0, 1440.0);
assert_eq!(
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
state
.sample_frame(&output, Duration::from_millis(8))
.windows[&id]
.presentation
.bounds
.current
.x,
state.sample_frame(&output, Duration::from_millis(8)).windows[&id]
state
.sample_frame(&output, Duration::from_millis(8))
.windows[&id]
.geometry
.visual
.current
@@ -970,7 +1090,10 @@ mod tests {
);
let inactive = state.workspaces.create_workspace();
state.output_workspaces.assign_workspace(output_id, inactive).unwrap();
state
.output_workspaces
.assign_workspace(output_id, inactive)
.unwrap();
let preview_id = WindowId(3);
state
.workspaces
@@ -1061,7 +1184,8 @@ mod tests {
width.set_target(500.);
let spring = SpringConfig::default();
let delta = Duration::from_millis(10);
let sampled_viewport = ViewportPresentation::from_motion(viewport).sample(delta, spring, true, false);
let sampled_viewport =
ViewportPresentation::from_motion(viewport).sample(delta, spring, true, false);
let predicted = predict_geometry(
geometry,
delta,
+184 -72
View File
@@ -42,27 +42,31 @@ fn tree_reflow_groups(
allow_overshoot: bool,
) -> HashMap<WindowId, usize> {
let margin = inner_gap.max(0.0) / 2.0;
let axis_bounds =
|position: f64, size: f64, target_position: f64, target_size: f64, speed: f64, size_speed: f64| {
let distance = target_position - position;
let size_distance = target_size - size;
let moving = distance != 0.0 || size_distance != 0.0 || speed != 0.0 || size_speed != 0.0;
let reversing =
(speed != 0.0 && distance * speed <= 0.0) || (size_speed != 0.0 && size_distance * size_speed <= 0.0);
// Endpoint bounds cannot contain overshoot or motion away from the
// target. Widen only that axis, preserving separation on a fixed axis.
if reversing || (allow_overshoot && moving) {
return (f64::NEG_INFINITY, f64::INFINITY);
}
// Proportional component velocities preserve a shared edge. Otherwise
// bound position and size separately to include intermediate excursions.
let end = if (speed * size_distance - size_speed * distance).abs() < 1e-6 {
(position + size).max(target_position + target_size)
} else {
position.max(target_position) + size.max(target_size)
};
(position.min(target_position) - margin, end + margin)
let axis_bounds = |position: f64,
size: f64,
target_position: f64,
target_size: f64,
speed: f64,
size_speed: f64| {
let distance = target_position - position;
let size_distance = target_size - size;
let moving = distance != 0.0 || size_distance != 0.0 || speed != 0.0 || size_speed != 0.0;
let reversing = (speed != 0.0 && distance * speed <= 0.0)
|| (size_speed != 0.0 && size_distance * size_speed <= 0.0);
// Endpoint bounds cannot contain overshoot or motion away from the
// target. Widen only that axis, preserving separation on a fixed axis.
if reversing || (allow_overshoot && moving) {
return (f64::NEG_INFINITY, f64::INFINITY);
}
// Proportional component velocities preserve a shared edge. Otherwise
// bound position and size separately to include intermediate excursions.
let end = if (speed * size_distance - size_speed * distance).abs() < 1e-6 {
(position + size).max(target_position + target_size)
} else {
position.max(target_position) + size.max(target_size)
};
(position.min(target_position) - margin, end + margin)
};
let moving = motions
.into_iter()
.filter(|(_, source, target, velocity)| {
@@ -131,7 +135,11 @@ impl PresentationDependencies {
self.viewport.get(&workspace).cloned()
}
pub(super) fn restore_viewport(&mut self, workspace: WorkspaceId, saved: Option<ViewportDependencies>) {
pub(super) fn restore_viewport(
&mut self,
workspace: WorkspaceId,
saved: Option<ViewportDependencies>,
) {
if let Some(saved) = saved {
self.viewport.insert(workspace, saved);
} else {
@@ -140,7 +148,12 @@ impl PresentationDependencies {
}
#[cfg(test)]
pub(super) fn wait_for_viewport(&mut self, workspace: WorkspaceId, id: WindowId, serial: Serial) {
pub(super) fn wait_for_viewport(
&mut self,
workspace: WorkspaceId,
id: WindowId,
serial: Serial,
) {
self.viewport.insert(
workspace,
ViewportDependencies {
@@ -202,7 +215,8 @@ impl PresentationDependencies {
self.viewport.retain(|_, waits| {
waits.waits.retain(|wait| Self::pending(*wait, windows));
for step in &mut waits.steps {
step.inputs.retain(|input| Self::pending(input.commit, windows));
step.inputs
.retain(|input| Self::pending(input.commit, windows));
}
if waits.steps.iter().all(|step| step.inputs.is_empty()) {
waits.steps.clear();
@@ -222,9 +236,9 @@ fn viewport_dependencies(
return ViewportDependencies::default();
};
let live = |input: &ViewportCommit| {
pending
.iter()
.any(|(window, transaction)| *window == input.commit.window && transaction.serial() == input.commit.serial)
pending.iter().any(|(window, transaction)| {
*window == input.commit.window && transaction.serial() == input.commit.serial
})
};
for step in &mut previous.steps {
for input in &mut step.inputs {
@@ -238,7 +252,11 @@ fn viewport_dependencies(
}
// Fold any prefix with no current configure inputs to the numeric start
// already stored in the next request. No obsolete serial owns a recipe.
if let Some(first) = previous.steps.iter().position(|step| !step.inputs.is_empty()) {
if let Some(first) = previous
.steps
.iter()
.position(|step| !step.inputs.is_empty())
{
previous.steps.drain(..first);
} else {
previous.steps.clear();
@@ -257,7 +275,11 @@ fn viewport_dependencies(
})
})
.collect::<Vec<_>>();
if previous.steps.last().is_none_or(|step| step.target != *target) {
if previous
.steps
.last()
.is_none_or(|step| step.target != *target)
{
if !target.uses_previous_target() {
previous.steps.clear();
previous.overflow = false;
@@ -283,7 +305,9 @@ fn viewport_dependencies(
owners.extend(inputs.iter().map(|input| input.commit));
owners.sort_by_key(|owner| owner.window.0);
owners.dedup();
let evaluate = |only: Option<ClientCommit>, exclude: Option<ClientCommit>, retained: Option<&HashSet<WindowId>>| {
let evaluate = |only: Option<ClientCommit>,
exclude: Option<ClientCommit>,
retained: Option<&HashSet<WindowId>>| {
let mut start = None;
for step in &previous.steps {
let widths = step
@@ -316,7 +340,10 @@ fn viewport_dependencies(
// Retain a collective wait in that case, removing inputs that are not
// needed to keep the held target different from the requested target.
if previous.waits.is_empty() && (held - layout.viewport_x()).abs() > 0.001 {
let mut collective = owners.iter().map(|owner| owner.window).collect::<HashSet<_>>();
let mut collective = owners
.iter()
.map(|owner| owner.window)
.collect::<HashSet<_>>();
for owner in &owners {
collective.remove(&owner.window);
if (evaluate(None, None, Some(&collective)) - layout.viewport_x()).abs() <= 0.001 {
@@ -342,7 +369,9 @@ impl Ferese {
pub(super) fn release_presentation_dependencies(&mut self) {
let paused_reflow = self.presentation_dependencies.paused_reflow.clone();
self.rebuild_presentation_dependencies_with_release(true);
self.presentation_dependencies.paused_reflow.extend(paused_reflow);
self.presentation_dependencies
.paused_reflow
.extend(paused_reflow);
}
pub(super) fn rebuild_presentation_dependencies(&mut self) {
@@ -379,7 +408,8 @@ impl Ferese {
tree_reflow_groups(
motions,
self.gap_config.inner,
self.spring_config.crossing == ferese_animation::CrossingPolicy::AllowOvershoot,
self.spring_config.crossing
== ferese_animation::CrossingPolicy::AllowOvershoot,
),
))
})
@@ -392,9 +422,10 @@ impl Ferese {
continue;
};
let own_column = match &workspace.layout {
WorkspaceLayout::Scrolling(layout) => {
layout.columns().iter().position(|column| column.windows.contains(&id))
}
WorkspaceLayout::Scrolling(layout) => layout
.columns()
.iter()
.position(|column| column.windows.contains(&id)),
WorkspaceLayout::Tree(_) => None,
};
let mut waits = Vec::new();
@@ -419,12 +450,17 @@ impl Ferese {
.iter()
.position(|column| column.windows.contains(slow))
.is_some_and(|source| {
source == own || (source < own && transaction.width_changes())
source == own
|| (source < own && transaction.width_changes())
})
}),
WorkspaceLayout::Tree(_) => tree_groups.get(&workspace_id).is_some_and(|groups| {
groups.get(&id).is_some_and(|group| groups.get(slow) == Some(group))
}),
WorkspaceLayout::Tree(_) => {
tree_groups.get(&workspace_id).is_some_and(|groups| {
groups
.get(&id)
.is_some_and(|group| groups.get(slow) == Some(group))
})
}
});
if linked {
waits.push(commit);
@@ -450,7 +486,8 @@ impl Ferese {
.windows
.resizing()
.filter(|id| {
self.workspaces.workspace_for_window(**id) == Some(workspace.id) && layout.contains(**id)
self.workspaces.workspace_for_window(**id) == Some(workspace.id)
&& layout.contains(**id)
})
.map(|id| (*id, *self.windows.transaction(id).unwrap()))
.collect::<Vec<_>>(),
@@ -554,7 +591,10 @@ mod tests {
);
assert_eq!(groups[&WindowId(1)], groups[&WindowId(2)]);
let groups = tree_reflow_groups(
[(WindowId(1), source, target), (WindowId(2), neighbor, moving_neighbor)],
[
(WindowId(1), source, target),
(WindowId(2), neighbor, moving_neighbor),
],
0.0,
);
assert_ne!(
@@ -566,7 +606,8 @@ mod tests {
fn nested_tree() -> ferese_layout::LayoutTree {
let mut tree = ferese_layout::LayoutTree::default();
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5).unwrap();
tree.insert(WindowId(1), None, Axis::Horizontal, 0.5)
.unwrap();
tree.insert(WindowId(3), Some(WindowId(1)), Axis::Horizontal, 0.5)
.unwrap();
tree.insert(WindowId(2), Some(WindowId(1)), Axis::Vertical, 0.5)
@@ -586,15 +627,18 @@ mod tests {
smart: false,
};
let mut tree = nested_tree();
let solve = |tree: &ferese_layout::LayoutTree, constraints: &HashMap<WindowId, SizeConstraints>| {
let solve = |tree: &ferese_layout::LayoutTree,
constraints: &HashMap<WindowId, SizeConstraints>| {
tree.geometry_with_constraints(bounds, gaps, constraints, Some(WindowId(1)))
.unwrap()
.geometry
};
let constraints = HashMap::new();
let source = solve(&tree, &constraints);
tree.resize_window(WindowId(1), Direction::Down, 0.1).unwrap();
tree.resize_window(WindowId(3), Direction::Down, 0.1).unwrap();
tree.resize_window(WindowId(1), Direction::Down, 0.1)
.unwrap();
tree.resize_window(WindowId(3), Direction::Down, 0.1)
.unwrap();
let target = solve(&tree, &constraints);
let group = |source: &HashMap<WindowId, Rect>, target: &HashMap<WindowId, Rect>| {
tree_reflow_groups(
@@ -611,7 +655,8 @@ mod tests {
// A horizontal request walks through the vertical child to the
// root split: both branches' actual x/width now participate.
tree.resize_window(WindowId(1), Direction::Right, 0.1).unwrap();
tree.resize_window(WindowId(1), Direction::Right, 0.1)
.unwrap();
let ancestor = solve(&tree, &constraints);
let groups = group(&source, &ancestor);
assert!(groups.values().all(|group| *group == groups[&WindowId(1)]));
@@ -649,7 +694,9 @@ mod tests {
)]);
let clipped_source = solve(&tree, &maximum);
let mut wider = tree.clone();
wider.resize_window(WindowId(1), Direction::Right, 0.1).unwrap();
wider
.resize_window(WindowId(1), Direction::Right, 0.1)
.unwrap();
let clipped_target = solve(&wider, &maximum);
assert_eq!(clipped_source[&WindowId(1)], clipped_target[&WindowId(1)]);
assert!(!group(&clipped_source, &clipped_target).contains_key(&WindowId(1)));
@@ -677,7 +724,9 @@ mod tests {
source[3],
];
let groups = tree_reflow_groups(
ids.into_iter().enumerate().map(|(i, id)| (id, source[i], target[i])),
ids.into_iter()
.enumerate()
.map(|(i, id)| (id, source[i], target[i])),
0.0,
);
assert_eq!(
@@ -689,7 +738,8 @@ mod tests {
let mut tree = nested_tree();
tree.stack_window(WindowId(2), WindowId(1)).unwrap();
let before = tree.geometry(Rect::new(0.0, 0.0, 800.0, 600.0)).unwrap();
tree.resize_window(WindowId(2), Direction::Right, 0.1).unwrap();
tree.resize_window(WindowId(2), Direction::Right, 0.1)
.unwrap();
let after = tree.geometry(Rect::new(0.0, 0.0, 800.0, 600.0)).unwrap();
let groups = tree_reflow_groups(
before
@@ -721,14 +771,19 @@ mod tests {
layout.set_focus_strategy(ViewportFocusStrategy::Center);
layout.insert(a, None).unwrap();
layout.insert(b, Some(a)).unwrap();
layout.set_column_width(a, ColumnWidth::Fixed(150.0)).unwrap();
layout.set_column_width(b, ColumnWidth::Fixed(300.0)).unwrap();
layout
.set_column_width(a, ColumnWidth::Fixed(150.0))
.unwrap();
layout
.set_column_width(b, ColumnWidth::Fixed(300.0))
.unwrap();
layout
.resolve_geometry_with_constraints(bounds, gaps, &constraints, Some(b))
.unwrap();
let pending = [(
a,
ResizeTransaction::new(10.into(), Duration::ZERO).with_column_width(Some(100.0), Some(150.0)),
ResizeTransaction::new(10.into(), Duration::ZERO)
.with_column_width(Some(100.0), Some(150.0)),
)];
let first = viewport_dependencies(&layout, &pending, Default::default());
assert_eq!(first.waits.len(), 1);
@@ -753,11 +808,13 @@ mod tests {
let pending = [
(
a,
ResizeTransaction::new(10.into(), Duration::ZERO).with_column_width(Some(100.0), Some(40.0)),
ResizeTransaction::new(10.into(), Duration::ZERO)
.with_column_width(Some(100.0), Some(40.0)),
),
(
b,
ResizeTransaction::new(20.into(), Duration::ZERO).with_column_width(Some(100.0), Some(40.0)),
ResizeTransaction::new(20.into(), Duration::ZERO)
.with_column_width(Some(100.0), Some(40.0)),
),
];
assert!(!layout.viewport_depends_on_width(a, 100.0, 40.0));
@@ -765,19 +822,31 @@ mod tests {
let both = viewport_dependencies(&layout, &pending, Default::default());
assert_eq!(both.waits.len(), 2, "joint held widths select another page");
let partial = viewport_dependencies(&layout, &pending[1..], both);
assert!(partial.waits.is_empty(), "one remaining held width fits this page");
assert!(viewport_dependencies(&layout, &[], partial).steps.is_empty());
assert!(
partial.waits.is_empty(),
"one remaining held width fits this page"
);
assert!(
viewport_dependencies(&layout, &[], partial)
.steps
.is_empty()
);
// Independent absolute requests reset history. Relative request storage
// is capped even if a client keeps replacing its configure.
layout.set_focus_strategy(ViewportFocusStrategy::Center);
layout.set_column_width(a, ColumnWidth::Fixed(150.0)).unwrap();
layout.set_column_width(b, ColumnWidth::Fixed(300.0)).unwrap();
layout
.set_column_width(a, ColumnWidth::Fixed(150.0))
.unwrap();
layout
.set_column_width(b, ColumnWidth::Fixed(300.0))
.unwrap();
layout
.resolve_geometry_with_constraints(bounds, gaps, &constraints, Some(b))
.unwrap();
let pending = [(
a,
ResizeTransaction::new(30.into(), Duration::ZERO).with_column_width(Some(100.0), Some(150.0)),
ResizeTransaction::new(30.into(), Duration::ZERO)
.with_column_width(Some(100.0), Some(150.0)),
)];
let mut graph = viewport_dependencies(&layout, &pending, Default::default());
for index in 0..80 {
@@ -790,7 +859,10 @@ mod tests {
assert!(graph.steps.len() <= MAX_VIEWPORT_STEPS);
}
assert!(graph.overflow);
assert!(!graph.waits.is_empty(), "bounded fallback must not free linked input");
assert!(
!graph.waits.is_empty(),
"bounded fallback must not free linked input"
);
layout.center_window(a, bounds, gaps, &constraints).unwrap();
graph = viewport_dependencies(&layout, &pending, graph);
assert_eq!(graph.steps.len(), 1);
@@ -807,7 +879,10 @@ mod tests {
windows.register("b", b);
windows.register("neighbor", neighbor);
windows.set_transaction(a, ResizeTransaction::new(10.into(), Duration::ZERO));
windows.set_transaction(b, ResizeTransaction::new(20.into(), Duration::from_millis(100)));
windows.set_transaction(
b,
ResizeTransaction::new(20.into(), Duration::from_millis(100)),
);
let commit = |window, serial| ClientCommit {
window,
serial: Serial::from(serial),
@@ -815,24 +890,34 @@ mod tests {
let workspace = WorkspaceId(1);
let mut dependencies = PresentationDependencies::default();
dependencies.reflow.insert(a, vec![commit(a, 10)]);
dependencies.reflow.insert(neighbor, vec![commit(a, 10), commit(b, 20)]);
dependencies
.reflow
.insert(neighbor, vec![commit(a, 10), commit(b, 20)]);
dependencies.wait_for_viewport(workspace, b, 20.into());
assert!(dependencies.reflow_blocked(neighbor, &windows));
assert!(!dependencies.reflow_blocked(b, &windows));
assert!(dependencies.viewport_blocked(workspace, &windows));
dependencies.holds(&windows);
// A replacement cannot be released by the older committed serial.
windows.set_transaction(b, ResizeTransaction::new(21.into(), Duration::from_millis(150)));
windows.set_transaction(
b,
ResizeTransaction::new(21.into(), Duration::from_millis(150)),
);
assert!(!windows.transaction(&b).unwrap().accepts(Some(20.into())));
assert!(!dependencies.viewport_blocked(workspace, &windows));
dependencies.wait_for_viewport(workspace, b, 21.into());
dependencies.reflow.insert(neighbor, vec![commit(a, 10), commit(b, 21)]);
dependencies
.reflow
.insert(neighbor, vec![commit(a, 10), commit(b, 21)]);
windows.expire_transactions(Duration::from_millis(300));
assert!(windows.transaction(&a).is_none());
assert!(windows.transaction(&b).is_some());
assert!(dependencies.reflow_blocked(neighbor, &windows));
let held = dependencies.holds(&windows);
assert!(held.contains(&a), "release tick must not charge waiting time");
assert!(
held.contains(&a),
"release tick must not charge waiting time"
);
let held = dependencies.holds(&windows);
assert!(!held.contains(&a));
// Unmap removes the serial owner; both properties retire on the tick.
@@ -864,11 +949,15 @@ mod tests {
WindowId(6),
];
for (i, id) in ids.iter().enumerate() {
layout.insert(*id, i.checked_sub(1).map(|j| ids[j])).unwrap();
layout
.insert(*id, i.checked_sub(1).map(|j| ids[j]))
.unwrap();
}
for (id, width) in ids.into_iter().zip([40.0, 40.0, 80.0, 80.0, 40.0, 40.0]) {
layout.set_column_width(id, ColumnWidth::Fixed(width)).unwrap();
layout
.set_column_width(id, ColumnWidth::Fixed(width))
.unwrap();
}
layout.focus(ids[4]).unwrap();
@@ -893,7 +982,13 @@ mod tests {
)
})
.collect::<Vec<_>>();
assert_eq!(layout.viewport_target().unwrap().with_held_widths(&widths, None), 320.0);
assert_eq!(
layout
.viewport_target()
.unwrap()
.with_held_widths(&widths, None),
320.0
);
let graph = viewport_dependencies(&layout, &pending, Default::default());
assert!(
!graph.waits.is_empty(),
@@ -925,7 +1020,10 @@ mod tests {
(*id, source, target)
})
.collect::<Vec<_>>();
let held = layout.viewport_target().unwrap().with_held_widths(&held_widths, None);
let held = layout
.viewport_target()
.unwrap()
.with_held_widths(&held_widths, None);
let partial = viewport_dependencies(&layout, &remaining, graph.clone());
if (held - layout.viewport_x()).abs() > 0.001 {
assert!(
@@ -958,9 +1056,23 @@ mod tests {
viewport.retarget(layout.viewport_x(), true);
viewport.reconcile_hold(true, false);
viewport.reconcile_hold(blocked, true);
viewport.advance(Duration::from_secs(3), SpringConfig::default(), true, blocked);
assert_eq!(viewport.motion().current, 0.0, "release consumed waiting time: {mask}");
viewport.advance(Duration::from_millis(16), SpringConfig::default(), true, blocked);
viewport.advance(
Duration::from_secs(3),
SpringConfig::default(),
true,
blocked,
);
assert_eq!(
viewport.motion().current,
0.0,
"release consumed waiting time: {mask}"
);
viewport.advance(
Duration::from_millis(16),
SpringConfig::default(),
true,
blocked,
);
assert_eq!(
viewport.motion().current == 0.0,
blocked,
+126 -35
View File
@@ -44,7 +44,10 @@ pub(crate) struct DesktopChanges {
impl Ferese {
pub(crate) fn begin_desktop_transition(&mut self) {
assert!(self.desktop_transition.is_none(), "nested desktop transition");
assert!(
self.desktop_transition.is_none(),
"nested desktop transition"
);
self.desktop_transition = Some(DesktopTransition::default());
}
@@ -57,12 +60,23 @@ impl Ferese {
assert!(self.desktop_transition.is_some());
let inventory = runtime
.iter()
.map(|output| (self.ensure_output_identity(&output.identity), output.geometry()))
.map(|output| {
(
self.ensure_output_identity(&output.identity),
output.geometry(),
)
})
.collect::<Vec<_>>();
let workspaces = self.workspaces.iter().map(|workspace| workspace.id).collect::<Vec<_>>();
let plan = self
.output_workspaces
.plan_desktop(&inventory, &workspaces, self.workspaces.next_workspace_id())?;
let workspaces = self
.workspaces
.iter()
.map(|workspace| workspace.id)
.collect::<Vec<_>>();
let plan = self.output_workspaces.plan_desktop(
&inventory,
&workspaces,
self.workspaces.next_workspace_id(),
)?;
let mut changes = DesktopChanges {
layout: !plan.affected_outputs.is_empty(),
focus: plan.focus_changed,
@@ -83,8 +97,13 @@ impl Ferese {
let old_owner = self.output_workspaces.output_for_workspace(workspace);
let old = old_owner.and_then(|owner| self.output_workspaces.geometry(owner));
(old_owner != Some(destination) || old != Some(new)).then(|| {
let bounds = |g: OutputGeometry| Rect::new(g.x as f64, g.y as f64, g.width as f64, g.height as f64);
(*id, moved_floating_rect(rect, bounds(old.unwrap_or(new)), bounds(new)))
let bounds = |g: OutputGeometry| {
Rect::new(g.x as f64, g.y as f64, g.width as f64, g.height as f64)
};
(
*id,
moved_floating_rect(rect, bounds(old.unwrap_or(new)), bounds(new)),
)
})
})
.collect::<Vec<_>>();
@@ -94,7 +113,10 @@ impl Ferese {
for id in plan.created_workspaces {
assert_eq!(self.workspaces.create_workspace(), id);
}
let surviving = runtime.iter().map(|output| output.output.clone()).collect::<Vec<_>>();
let surviving = runtime
.iter()
.map(|output| output.output.clone())
.collect::<Vec<_>>();
let old_outputs = self.output_ids.keys().cloned().collect::<Vec<_>>();
for output in old_outputs {
if !surviving.contains(&output) {
@@ -111,10 +133,16 @@ impl Ferese {
for (output, (id, geometry)) in runtime.into_iter().zip(inventory) {
let state_changed = output.output.current_mode() != Some(output.mode)
|| output.output.current_transform() != output.transform
|| output.output.current_scale().fractional_scale() != output.scale.fractional_scale()
|| self.space.output_geometry(&output.output).is_none_or(|old| {
old.loc != output.position || old.size.w != geometry.width || old.size.h != geometry.height
});
|| output.output.current_scale().fractional_scale()
!= output.scale.fractional_scale()
|| self
.space
.output_geometry(&output.output)
.is_none_or(|old| {
old.loc != output.position
|| old.size.w != geometry.width
|| old.size.h != geometry.height
});
if state_changed {
output.output.change_current_state(
Some(output.mode),
@@ -168,7 +196,8 @@ impl Ferese {
self.desktop_transition.as_mut().unwrap().publishing = true;
let activity_output = self.space.outputs().next().map(|output| output.name());
if let Some(output) = activity_output
&& (self.activities.store.attach_output(&output) | self.activities.pomodoro_attach_output(&output))
&& (self.activities.store.attach_output(&output)
| self.activities.pomodoro_attach_output(&output))
{
self.activities.changed();
}
@@ -190,13 +219,17 @@ impl Ferese {
}
changes.outputs.retain(|output| {
self.output_ids.contains_key(output)
|| self
.direct_backend
.as_ref()
.is_some_and(|backend| backend.physical_outputs().any(|physical| physical == output))
|| self.direct_backend.as_ref().is_some_and(|backend| {
backend
.physical_outputs()
.any(|physical| physical == output)
})
});
if !changes.outputs.is_empty() {
crate::backends::direct::render_on(self, &changes.outputs.into_iter().collect::<Vec<_>>());
crate::backends::direct::render_on(
self,
&changes.outputs.into_iter().collect::<Vec<_>>(),
);
}
}
}
@@ -241,7 +274,10 @@ mod tests {
}
}
fn window(state: &mut Ferese, event_loop: &mut EventLoop<'static, Ferese>) -> (Window, UnixStream) {
fn window(
state: &mut Ferese,
event_loop: &mut EventLoop<'static, Ferese>,
) -> (Window, UnixStream) {
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_wm_base::XdgWmBase;
use smithay::reexports::wayland_server::protocol::wl_compositor::WlCompositor;
use smithay::wayland::shell::xdg::XdgWmBaseUserData;
@@ -258,12 +294,18 @@ mod tests {
)
.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();
request(&mut wire, compositor.id().protocol_id(), 0, &[2]);
request(&mut wire, shell.id().protocol_id(), 2, &[3, 2]);
request(&mut wire, 3, 1, &[4]);
event_loop.dispatch(Duration::from_millis(10), state).unwrap();
event_loop
.dispatch(Duration::from_millis(10), state)
.unwrap();
let window = state
.space
.elements()
@@ -410,7 +452,10 @@ mod tests {
events(&mut state, &mut shell_wire);
let before_serial = state.shell_snapshot_serial;
let spare = state.workspaces.create_workspace();
state.output_workspaces.assign_workspace(b_id, spare).unwrap();
state
.output_workspaces
.assign_workspace(b_id, spare)
.unwrap();
state.workspace_slides.insert(
b_id,
WorkspaceSlide::new(None, spare, b_workspace, SwipeDirection::Right),
@@ -423,14 +468,20 @@ mod tests {
state.workspaces.placement(slow_id),
Some(WindowPlacement::Floating { rect: initial })
);
assert_eq!(state.output_workspaces.output_for_workspace(b_workspace), Some(b_id));
assert_eq!(
state.output_workspaces.output_for_workspace(b_workspace),
Some(b_id)
);
state.send_shell_snapshots();
assert!(events(&mut state, &mut shell_wire).is_empty());
let mut runtime = state.current_desktop_outputs();
runtime.retain(|entry| entry.output == a_handle);
runtime[0].mode.size = (500, 400).into();
let changes = state.publish_desktop(runtime).unwrap();
assert_eq!(state.output_workspaces.output_for_workspace(b_workspace), Some(a_id));
assert_eq!(
state.output_workspaces.output_for_workspace(b_workspace),
Some(a_id)
);
assert_eq!(state.focused_window, Some(slow_id));
assert!(!state.workspace_slides.contains_key(&b_id));
state.send_shell_snapshots();
@@ -442,9 +493,27 @@ mod tests {
.iter()
.filter(|(id, _, _)| *id == shell.id().protocol_id())
.collect::<Vec<_>>();
assert_eq!(shell_events.iter().filter(|(_, opcode, _)| *opcode == 0).count(), 1);
assert_eq!(shell_events.iter().filter(|(_, opcode, _)| *opcode == 4).count(), 1);
assert_eq!(shell_events.iter().filter(|(_, opcode, _)| *opcode == 2).count(), 1);
assert_eq!(
shell_events
.iter()
.filter(|(_, opcode, _)| *opcode == 0)
.count(),
1
);
assert_eq!(
shell_events
.iter()
.filter(|(_, opcode, _)| *opcode == 4)
.count(),
1
);
assert_eq!(
shell_events
.iter()
.filter(|(_, opcode, _)| *opcode == 2)
.count(),
1
);
for (_, opcode, args) in &shell_events {
if *opcode == 3 {
assert_eq!(args[3], a_id.0 as u32);
@@ -460,16 +529,27 @@ mod tests {
assert_eq!(args[3], b_workspace.0 as u32);
}
}
let WindowPlacement::Floating { rect } = state.workspaces.placement(slow_id).unwrap() else {
let WindowPlacement::Floating { rect } = state.workspaces.placement(slow_id).unwrap()
else {
panic!()
};
assert!(rect.x >= 0.0 && rect.y >= 0.0 && rect.x + rect.width <= 500.0 && rect.y + rect.height <= 400.0);
assert!(
rect.x >= 0.0
&& rect.y >= 0.0
&& rect.x + rect.width <= 500.0
&& rect.y + rect.height <= 400.0
);
assert!(
state.windows.transaction(&slow_id).is_some(),
"topology commit must not await XDG acknowledgement"
);
assert_eq!(
state.windows.geometry(&slow_id).unwrap().client.committed_size,
state
.windows
.geometry(&slow_id)
.unwrap()
.client
.committed_size,
Some(ClientSize::from_rect(initial))
);
@@ -506,7 +586,10 @@ mod tests {
}
assert_eq!(state.output_workspaces, before);
assert!(events(&mut state, &mut shell_wire).is_empty());
assert_eq!(state.workspaces.workspace_for_window(slow_id), Some(b_workspace));
assert_eq!(
state.workspaces.workspace_for_window(slow_id),
Some(b_workspace)
);
let unfocused = state.outputs_by_id[&c_id].clone();
let focused = state.focused_window;
@@ -531,7 +614,9 @@ mod tests {
)
.unwrap();
request(&mut lock_wire, lock.id().protocol_id(), 1, &[2]);
event_loop.dispatch(Duration::from_millis(10), &mut state).unwrap();
event_loop
.dispatch(Duration::from_millis(10), &mut state)
.unwrap();
assert!(state.session_lock.active());
state.begin_desktop_transition();
for output in state.output_ids.keys().cloned().collect::<Vec<_>>() {
@@ -545,11 +630,17 @@ mod tests {
assert!(state.focused_window.is_none());
assert!(state.workspace_slides.is_empty());
assert!(state.seat.get_keyboard().unwrap().current_focus().is_none());
assert_eq!(state.workspaces.workspace_for_window(slow_id), Some(b_workspace));
assert_eq!(
state.workspaces.workspace_for_window(slow_id),
Some(b_workspace)
);
state.begin_desktop_transition();
let changes = state.publish_desktop(vec![output("a", 0, 1200)]).unwrap();
state.finish_desktop_transition(changes);
assert_eq!(state.output_workspaces.output_for_workspace(b_workspace), Some(a_id));
assert_eq!(
state.output_workspaces.output_for_workspace(b_workspace),
Some(a_id)
);
assert!(state.session_lock.active());
assert!(state.surface_under((20.0, 20.0).into()).is_none());
}
+83 -19
View File
@@ -65,8 +65,19 @@ pub(crate) fn contains(rect: Rect, p: Point<f64, Logical>) -> bool {
}
impl IndicatorGeometry {
pub fn new(content: Rect, output: Rect, left_boundary: f64, count: usize, c: TabIndicatorSettings) -> Option<Self> {
if count < 2 || content.width <= 0.0 || content.height <= 0.0 || output.width <= 0.0 || output.height <= 0.0 {
pub fn new(
content: Rect,
output: Rect,
left_boundary: f64,
count: usize,
c: TabIndicatorSettings,
) -> Option<Self> {
if count < 2
|| content.width <= 0.0
|| content.height <= 0.0
|| output.width <= 0.0
|| output.height <= 0.0
{
return None;
}
let left = content.x.max(output.x);
@@ -83,19 +94,23 @@ impl IndicatorGeometry {
left
}
.clamp(output.x, (right - c.width).max(output.x));
let natural = count as f64 * c.segment_height + count.saturating_sub(1) as f64 * c.segment_gap;
let natural =
count as f64 * c.segment_height + count.saturating_sub(1) as f64 * c.segment_gap;
let fit = ((bottom - top) / natural).min(1.0);
let height = (natural * fit).min(bottom - top);
let rail = Rect::new(x, top + (bottom - top - height) * 0.5, c.width, height);
let hit_width = c.hit_width.min(output.width);
let hit = Rect::new(
(x + c.width * 0.5 - hit_width * 0.5).clamp(output.x, output.x + output.width - hit_width),
(x + c.width * 0.5 - hit_width * 0.5)
.clamp(output.x, output.x + output.width - hit_width),
rail.y,
hit_width,
rail.height,
);
let padding = c.padding.min(output.height * 0.1).min(output.width * 0.1);
let row_height = c.row_height.min((output.height - 2.0 * padding).max(f64::EPSILON));
let row_height = c
.row_height
.min((output.height - 2.0 * padding).max(f64::EPSILON));
let rows = (((output.height - 2.0 * padding) / row_height).floor() as usize)
.max(1)
.min(count);
@@ -110,7 +125,8 @@ impl IndicatorGeometry {
};
let list = Rect::new(
x,
(rail.y + rail.height * 0.5 - list_height * 0.5).clamp(output.y, output.y + output.height - list_height),
(rail.y + rail.height * 0.5 - list_height * 0.5)
.clamp(output.y, output.y + output.height - list_height),
list_width,
list_height,
);
@@ -175,7 +191,12 @@ impl Ferese {
}
let output = self.outputs_by_id.get(&output_id)?;
let g = self.space.output_geometry(output)?;
let bounds = Rect::new(g.loc.x as f64, g.loc.y as f64, g.size.w as f64, g.size.h as f64);
let bounds = Rect::new(
g.loc.x as f64,
g.loc.y as f64,
g.size.w as f64,
g.size.h as f64,
);
let mut boundary = bounds.x;
// Measure the current visible gap, including animated column positions.
for window in self.space.elements() {
@@ -215,7 +236,9 @@ impl Ferese {
|| self.input_capture.active()
|| self.overview.is_presenting()
|| self.seat.get_pointer().is_some_and(|p| p.is_grabbed())
|| self.layer_surface_under(p, &[Layer::Overlay, Layer::Top]).is_some()
|| self
.layer_surface_under(p, &[Layer::Overlay, Layer::Top])
.is_some()
{
return false;
}
@@ -228,7 +251,10 @@ impl Ferese {
if id == active {
break;
}
if self.presented_window_rect(id).is_some_and(|r| contains(r, p)) {
if self
.presented_window_rect(id)
.is_some_and(|r| contains(r, p))
{
return false;
}
}
@@ -282,11 +308,19 @@ impl Ferese {
.first_row
.min(bar.group.windows.len().saturating_sub(g.rows));
let index = ((p.y - g.list.y - g.padding).max(0.0) / g.row_height).floor() as usize;
return bar.group.windows.get(first + index.min(g.rows - 1)).copied();
return bar
.group
.windows
.get(first + index.min(g.rows - 1))
.copied();
}
if contains(g.hit, p) {
let index = ((p.y - g.rail.y) / g.pitch).floor() as usize;
return bar.group.windows.get(index.min(bar.group.windows.len() - 1)).copied();
return bar
.group
.windows
.get(index.min(bar.group.windows.len() - 1))
.copied();
}
if expanded && g.hover_contains(p) {
return Some(bar.group.active);
@@ -314,13 +348,24 @@ impl Ferese {
}
self.advance_animations(Instant::now());
if let Some(anchor) = anchor {
let same = self.tab_indicator.hover.as_ref().is_some_and(|h| h.anchor == anchor);
let same = self
.tab_indicator
.hover
.as_ref()
.is_some_and(|h| h.anchor == anchor);
if !same {
let selected = self
.scrolling_tab_bar(anchor)
.and_then(|bar| {
let g = self.tab_indicator_geometry(bar, self.presented_window_rect(bar.group.active)?)?;
let selected = bar.group.windows.iter().position(|id| *id == bar.group.active)?;
let g = self.tab_indicator_geometry(
bar,
self.presented_window_rect(bar.group.active)?,
)?;
let selected = bar
.group
.windows
.iter()
.position(|id| *id == bar.group.active)?;
Some(
selected
.saturating_sub(g.rows / 2)
@@ -420,7 +465,10 @@ impl Ferese {
let WorkspaceLayout::Scrolling(layout) = &workspace.layout else {
return;
};
let Some(group) = layout.tab_group(current).filter(|g| g.windows.contains(&previous)) else {
let Some(group) = layout
.tab_group(current)
.filter(|g| g.windows.contains(&previous))
else {
return;
};
if self.tab_indicator_settings.flash_duration.is_zero() {
@@ -430,7 +478,12 @@ impl Ferese {
if let Some(token) = self.tab_indicator.timer.take() {
self.loop_handle.remove(token);
}
if let Some(flash) = self.tab_indicator.flash.as_mut().filter(|f| f.anchor == anchor) {
if let Some(flash) = self
.tab_indicator
.flash
.as_mut()
.filter(|f| f.anchor == anchor)
{
flash.opacity.retarget_preserving_motion(1.0);
} else {
let mut opacity = AnimatedValue::new(0.0);
@@ -466,7 +519,10 @@ impl Ferese {
}
pub(crate) fn reconcile_tab_indicator(&mut self) {
if self.session_lock.active() || self.input_capture.active() || self.overview.is_presenting() {
if self.session_lock.active()
|| self.input_capture.active()
|| self.overview.is_presenting()
{
self.clear_tab_indicator();
return;
}
@@ -571,9 +627,17 @@ mod tests {
state.hover.as_mut().unwrap().opacity.set_target(1.);
state.advance(Duration::from_millis(30), SpringConfig::default(), true);
let before = state.hover.as_ref().unwrap().opacity;
state.hover.as_mut().unwrap().opacity.retarget_preserving_motion(0.);
state
.hover
.as_mut()
.unwrap()
.opacity
.retarget_preserving_motion(0.);
let after = state.hover.as_ref().unwrap().opacity;
assert_eq!((before.current, before.velocity), (after.current, after.velocity));
assert_eq!(
(before.current, before.velocity),
(after.current, after.velocity)
);
for _ in 0..300 {
state.advance(Duration::from_millis(16), SpringConfig::default(), true);
}
+36 -14
View File
@@ -12,7 +12,10 @@ impl Ferese {
if workspace.fullscreen.is_some() {
return;
}
let Ok(Some(target)) = workspace.layout.directional_neighbor(window, direction, bounds) else {
let Ok(Some(target)) = workspace
.layout
.directional_neighbor(window, direction, bounds)
else {
return;
};
if let WorkspaceLayout::Scrolling(layout) = &mut self.workspaces.active_mut().layout {
@@ -71,24 +74,38 @@ impl Ferese {
if self.workspaces.active().fullscreen.is_some() {
return;
}
if let Some(target) = self.workspaces.active().layout.adjacent_tab(window, previous)
if let Some(target) = self
.workspaces
.active()
.layout
.adjacent_tab(window, previous)
&& self.activate_managed_window(target)
{
self.flash_tab_switch(window, target);
}
}
pub(crate) fn visible_tab_bars(&self, workspace: WorkspaceId) -> impl Iterator<Item = &ferese_layout::TabBar> {
self.tab_bars.get(&workspace).into_iter().flatten().filter(move |bar| {
self.workspaces
.workspace(workspace)
.is_some_and(|ws| ws.fullscreen.is_none())
&& self.windows.record(bar.group.active).is_some_and(|w| !w.maximized)
&& self
.windows
.window(bar.group.active)
.is_some_and(|w| self.space.element_location(w).is_some())
})
pub(crate) fn visible_tab_bars(
&self,
workspace: WorkspaceId,
) -> impl Iterator<Item = &ferese_layout::TabBar> {
self.tab_bars
.get(&workspace)
.into_iter()
.flatten()
.filter(move |bar| {
self.workspaces
.workspace(workspace)
.is_some_and(|ws| ws.fullscreen.is_none())
&& self
.windows
.record(bar.group.active)
.is_some_and(|w| !w.maximized)
&& self
.windows
.window(bar.group.active)
.is_some_and(|w| self.space.element_location(w).is_some())
})
}
pub(crate) fn tab_at(&self, position: Point<f64, Logical>) -> Option<WindowId> {
@@ -114,7 +131,12 @@ impl Ferese {
}
self.visible_tab_bars(workspace).find_map(|bar| {
let content = self.presented_window_rect(bar.group.active)?;
let rect = Rect::new(content.x, content.y - bar.rect.height, content.width, bar.rect.height);
let rect = Rect::new(
content.x,
content.y - bar.rect.height,
content.width,
bar.rect.height,
);
tab_hit(&bar.group.windows, rect, position.x, position.y)
})
}
+45 -12
View File
@@ -107,7 +107,9 @@ impl ViewportPresentation {
pub(super) fn reconcile_hold(&mut self, blocked: bool, release_tick: bool) {
let mode = std::mem::replace(&mut self.mode, ViewportMode::Running(ViewportOwner::Spring));
self.mode = match mode {
ViewportMode::Running(owner) | ViewportMode::Resuming(owner) if blocked => ViewportMode::Held(owner),
ViewportMode::Running(owner) | ViewportMode::Resuming(owner) if blocked => {
ViewportMode::Held(owner)
}
ViewportMode::Held(owner) if !blocked && !release_tick => ViewportMode::Running(owner),
mode => mode,
};
@@ -130,7 +132,8 @@ impl ViewportPresentation {
#[cfg(feature = "resize-metrics")]
pub(super) fn spring_is_moving(&self) -> bool {
self.gesture().is_none() && (self.motion.current != self.motion.target || self.motion.velocity != 0.0)
self.gesture().is_none()
&& (self.motion.current != self.motion.target || self.motion.velocity != 0.0)
}
// Prediction and authoritative ticks evaluate the same motion step. The
@@ -153,12 +156,14 @@ impl ViewportPresentation {
motion.velocity = 0.0;
true
}
ViewportMode::Running(ViewportOwner::Spring) | ViewportMode::Resuming(ViewportOwner::Spring)
ViewportMode::Running(ViewportOwner::Spring)
| ViewportMode::Resuming(ViewportOwner::Spring)
if animations =>
{
motion.advance(delta, spring)
}
ViewportMode::Running(ViewportOwner::Spring) | ViewportMode::Resuming(ViewportOwner::Spring) => {
ViewportMode::Running(ViewportOwner::Spring)
| ViewportMode::Resuming(ViewportOwner::Spring) => {
motion.snap();
false
}
@@ -167,16 +172,28 @@ impl ViewportPresentation {
(motion, active)
}
pub(super) fn advance(&mut self, delta: Duration, spring: SpringConfig, animations: bool, blocked: bool) -> bool {
pub(super) fn advance(
&mut self,
delta: Duration,
spring: SpringConfig,
animations: bool,
blocked: bool,
) -> bool {
let (motion, active) = self.step_motion(delta, spring, animations, blocked);
self.motion = motion;
let mode = std::mem::replace(&mut self.mode, ViewportMode::Running(ViewportOwner::Spring));
self.mode = match mode {
ViewportMode::Running(owner) | ViewportMode::Held(owner) | ViewportMode::Resuming(owner) if blocked => {
ViewportMode::Running(owner)
| ViewportMode::Held(owner)
| ViewportMode::Resuming(owner)
if blocked =>
{
ViewportMode::Held(owner)
}
ViewportMode::Held(owner) => ViewportMode::Resuming(owner),
ViewportMode::Running(owner) | ViewportMode::Resuming(owner) => ViewportMode::Running(owner),
ViewportMode::Running(owner) | ViewportMode::Resuming(owner) => {
ViewportMode::Running(owner)
}
};
active
}
@@ -198,7 +215,9 @@ impl ViewportPresentation {
impl Ferese {
pub(super) fn focus_swipe(&self) -> Option<&FocusSwipe> {
self.viewports.values().find_map(ViewportPresentation::gesture)
self.viewports
.values()
.find_map(ViewportPresentation::gesture)
}
}
@@ -261,7 +280,10 @@ mod tests {
viewport.retarget(700.0, true);
assert_eq!(viewport.motion.current, position);
assert_eq!(viewport.motion.velocity, velocity);
assert!(matches!(viewport.mode, ViewportMode::Running(ViewportOwner::Spring)));
assert!(matches!(
viewport.mode,
ViewportMode::Running(ViewportOwner::Spring)
));
let mut expected = viewport.motion;
expected.advance(TICK, SpringConfig::default());
viewport.advance(TICK, SpringConfig::default(), true, false);
@@ -349,7 +371,10 @@ mod tests {
viewport.reconcile_hold(true, false);
viewport.end_gesture(None).unwrap();
viewport.retarget(700.0, true);
assert!(matches!(viewport.mode, ViewportMode::Held(ViewportOwner::Spring)));
assert!(matches!(
viewport.mode,
ViewportMode::Held(ViewportOwner::Spring)
));
assert_eq!(viewport.motion.current, frozen.current);
assert_eq!(viewport.motion.velocity, frozen.velocity);
}
@@ -374,7 +399,10 @@ mod tests {
let mut advanced = state.clone();
advanced.advance(horizon, SpringConfig::default(), true, blocked);
assert_eq!(sample, advanced.presented_motion());
assert!(state == original, "prediction changed authoritative mode or motion");
assert!(
state == original,
"prediction changed authoritative mode or motion"
);
}
}
}
@@ -402,7 +430,12 @@ mod tests {
));
world.velocity = before.velocity.x + motion.velocity;
world.set_target(geometry.logical.x + motion.target);
super::super::animation::sync_scrolling_coordinates(&mut geometry, &mut world, &motion, false);
super::super::animation::sync_scrolling_coordinates(
&mut geometry,
&mut world,
&motion,
false,
);
assert!((geometry.visual.current.x - before.current.x).abs() < 1e-6);
assert!((geometry.visual.velocity.x - before.velocity.x).abs() < 1e-6);
assert_eq!(geometry.logical.x, world.target - motion.target);
+262 -70
View File
@@ -4,7 +4,11 @@ use smithay::input::pointer::{Focus, GrabStartData, MotionEvent};
use smithay::reexports::wayland_server::Resource;
use smithay::utils::SERIAL_COUNTER;
fn fixture() -> (smithay::reexports::calloop::EventLoop<'static, Ferese>, Ferese, Output) {
fn fixture() -> (
smithay::reexports::calloop::EventLoop<'static, Ferese>,
Ferese,
Output,
) {
let mut events = smithay::reexports::calloop::EventLoop::try_new().unwrap();
let mut state = crate::startup_tests::state(&mut events);
state.animations_enabled = false;
@@ -53,7 +57,9 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
.iter()
.map(|(window, _)| state.windows.ids()[window])
.collect::<Vec<_>>();
let [a, b, c, d] = ids.as_slice() else { unreachable!() };
let [a, b, c, d] = ids.as_slice() else {
unreachable!()
};
let (a, b, c, d) = (*a, *b, *c, *d);
let mut tree = ferese_layout::LayoutTree::default();
tree.insert(a, None, Axis::Horizontal, 0.5).unwrap();
@@ -94,7 +100,10 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(48));
let after = [a, b, c, d].map(|id| state.windows.geometry(&id).unwrap().visual.current);
assert_eq!(before[..2], after[..2], "linked left reflow must wait");
assert_ne!(before[2], after[2], "right branch must continue its existing motion");
assert_ne!(
before[2], after[2],
"right branch must continue its existing motion"
);
assert_ne!(before[3], after[3]);
let frame = state.sample_frame(&output, Duration::from_millis(16));
assert_eq!(frame.windows[&a].geometry.visual.current, after[0]);
@@ -105,7 +114,11 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
let location = state.space.element_location(window).unwrap();
assert_eq!(location.x, geometry.visual.current.x.round() as i32);
assert_eq!(location.y, geometry.visual.current.y.round() as i32);
let point = (geometry.visual.current.x + 5.0, geometry.visual.current.y + 5.0).into();
let point = (
geometry.visual.current.x + 5.0,
geometry.visual.current.y + 5.0,
)
.into();
let (surface, origin) = state.window_surface_under(point).unwrap();
assert_eq!(surface, *window.toplevel().unwrap().wl_surface());
assert!((origin.y - geometry.visual.current.y).abs() < 0.001);
@@ -120,8 +133,16 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
request(&mut clients[0].1, 2, 6, &[], None);
dispatch(&mut events, &mut state);
assert!(state.windows.transaction(&a).is_none());
assert!(state.presentation_dependencies.reflow_blocked(a, &state.windows));
assert!(state.presentation_dependencies.reflow_blocked(b, &state.windows));
assert!(
state
.presentation_dependencies
.reflow_blocked(a, &state.windows)
);
assert!(
state
.presentation_dependencies
.reflow_blocked(b, &state.windows)
);
state.advance_animations_at(Duration::from_millis(284), Duration::from_millis(332));
assert!(state.windows.transaction(&b).is_none());
assert_eq!(state.windows.geometry(&a).unwrap().visual.current, after[0]);
@@ -134,10 +155,17 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
request(&mut clients[0].1, 2, 1, &[0, 0, 0], None);
request(&mut clients[0].1, 2, 6, &[], None);
dispatch(&mut events, &mut state);
assert!(!state.presentation_dependencies.reflow_blocked(c, &state.windows));
assert!(
!state
.presentation_dependencies
.reflow_blocked(c, &state.windows)
);
state
.workspaces
.set_active_layout_mode(ferese_core::LayoutMode::Scrolling, Rect::new(0.0, 0.0, 800.0, 600.0))
.set_active_layout_mode(
ferese_core::LayoutMode::Scrolling,
Rect::new(0.0, 0.0, 800.0, 600.0),
)
.unwrap();
state.relayout();
for id in [c, d] {
@@ -145,7 +173,11 @@ fn tree_resize_does_not_hold_motion_in_an_unchanged_neighboring_branch() {
}
state.release_presentation_dependencies();
for id in [c, d] {
assert!(!state.presentation_dependencies.reflow_blocked(id, &state.windows));
assert!(
!state
.presentation_dependencies
.reflow_blocked(id, &state.windows)
);
}
}
@@ -187,7 +219,10 @@ fn width_cycle_keeps_stacked_rows_together_through_holds_forecasts_and_reversal(
state.release_presentation_dependencies();
state.advance_animations_at(Duration::ZERO, Duration::ZERO);
let before = state.windows.geometry(&a).unwrap().visual.current.width;
assert_eq!(state.windows.geometry(&b).unwrap().visual.current.width, before);
assert_eq!(
state.windows.geometry(&b).unwrap().visual.current.width,
before
);
state.animations_enabled = animations;
state.spring_config = SpringConfig {
stiffness: 900.0,
@@ -222,7 +257,11 @@ fn width_cycle_keeps_stacked_rows_together_through_holds_forecasts_and_reversal(
let second = *state.windows.geometry(&b).unwrap();
assert_eq!(first.visual.current.width, second.visual.current.width);
assert_eq!(first.visual.velocity.width, second.visual.velocity.width);
for horizon in [Duration::ZERO, Duration::from_millis(4), Duration::from_millis(16)] {
for horizon in [
Duration::ZERO,
Duration::from_millis(4),
Duration::from_millis(16),
] {
let scene = state.sample_frame(&output, horizon);
assert_eq!(
scene.windows[&a].geometry.visual.current.width,
@@ -236,11 +275,15 @@ fn width_cycle_keeps_stacked_rows_together_through_holds_forecasts_and_reversal(
state.windows.clear_transaction(&a);
state.release_presentation_dependencies();
assert_eq!(
state.presentation_dependencies.reflow_blocked(a, &state.windows),
state
.presentation_dependencies
.reflow_blocked(a, &state.windows),
animations
);
assert_eq!(
state.presentation_dependencies.reflow_blocked(b, &state.windows),
state
.presentation_dependencies
.reflow_blocked(b, &state.windows),
animations
);
for millis in [16, 100, 299, 300, 316, 332] {
@@ -262,11 +305,20 @@ fn width_cycle_keeps_stacked_rows_together_through_holds_forecasts_and_reversal(
}
state.release_presentation_dependencies();
for frame in 1..=150 {
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(332 + frame * 16));
state.advance_animations_at(
Duration::from_millis(16),
Duration::from_millis(332 + frame * 16),
);
assert_equal(&state);
}
assert_eq!(state.windows.geometry(&a).unwrap().visual.current.width, before);
assert_eq!(state.windows.geometry(&b).unwrap().visual.current.width, before);
assert_eq!(
state.windows.geometry(&a).unwrap().visual.current.width,
before
);
assert_eq!(
state.windows.geometry(&b).unwrap().visual.current.width,
before
);
}
}
@@ -292,9 +344,18 @@ fn floating_placement_reserves_the_full_opening_window_footprint() {
let obstacles = state.floating_obstacles(None, bounds);
assert_eq!(
obstacles,
vec![(id.0, crate::floating::intersection(logical, bounds).unwrap())]
vec![(
id.0,
crate::floating::intersection(logical, bounds).unwrap()
)]
);
let placed = crate::floating::min_overlap((80., 80.), bounds, &[obstacles[0].1], (logical.x, logical.y)).unwrap();
let placed = crate::floating::min_overlap(
(80., 80.),
bounds,
&[obstacles[0].1],
(logical.x, logical.y),
)
.unwrap();
assert!(crate::floating::intersection(placed, logical).is_none());
}
@@ -424,7 +485,12 @@ fn keyboard_floating_actions_preserve_maximized_and_fullscreen_restore_geometry(
state.set_window_maximized(id, true);
}
let geometry = state.windows.geometry(&id).unwrap().logical;
for direction in [Direction::Left, Direction::Right, Direction::Up, Direction::Down] {
for direction in [
Direction::Left,
Direction::Right,
Direction::Up,
Direction::Down,
] {
state.move_direction(direction);
state.resize_direction(direction);
assert_eq!(state.workspaces.placement(id), normal);
@@ -462,17 +528,29 @@ fn keyboard_floating_resize_reuses_the_client_barrier_and_move_keeps_it_pending(
let before = state.windows.geometry(&id).unwrap().visual.current;
let client = state.windows.geometry(&id).unwrap().client.committed_size;
state.resize_direction(Direction::Right);
let transaction = *state.windows.transaction(&id).expect("existing resize barrier");
let transaction = *state
.windows
.transaction(&id)
.expect("existing resize barrier");
assert_eq!(transaction.source_geometry(), Some(window.geometry()));
assert_eq!(state.windows.geometry(&id).unwrap().client.committed_size, client);
assert_eq!(
state.windows.geometry(&id).unwrap().client.committed_size,
client
);
assert_eq!(state.windows.geometry(&id).unwrap().visual.current, before);
assert_eq!(
state.windows.geometry(&id).unwrap().logical,
Rect::new(120., 100., 96., 48.)
);
state.move_direction(Direction::Right);
assert_eq!(state.windows.transaction(&id).unwrap().serial(), transaction.serial());
assert_eq!(state.windows.geometry(&id).unwrap().client.committed_size, client);
assert_eq!(
state.windows.transaction(&id).unwrap().serial(),
transaction.serial()
);
assert_eq!(
state.windows.geometry(&id).unwrap().client.committed_size,
client
);
assert_eq!(state.windows.geometry(&id).unwrap().visual.current, before);
assert_eq!(
state.windows.geometry(&id).unwrap().logical,
@@ -593,12 +671,24 @@ fn dmabuf_validation_uses_the_originating_global_without_a_desktop_frame() {
// being visited first. The mock capability accepts only B's buffers.
for (params, buffer) in [(5, None), (6, Some(7))] {
request(&mut wire, 4, 1, &[params], None);
request(&mut wire, params, 1, &[0, 0, 16, 0, 0], Some(file.as_raw_fd()));
request(
&mut wire,
params,
1,
&[0, 0, 16, 0, 0],
Some(file.as_raw_fd()),
);
let mut args = vec![4, 4, Fourcc::Argb8888 as u32, 0];
if let Some(buffer) = buffer {
args.insert(0, buffer);
}
request(&mut wire, params, if buffer.is_some() { 3 } else { 2 }, &args, None);
request(
&mut wire,
params,
if buffer.is_some() { 3 } else { 2 },
&args,
None,
);
dispatch(&mut events, &mut state);
assert!(state.space.outputs().next().is_none());
state.process_pending_dmabuf_imports();
@@ -619,10 +709,19 @@ fn dmabuf_validation_uses_the_originating_global_without_a_desktop_frame() {
state.display_handle.flush_clients().unwrap();
let response = read(&mut wire);
if buffer.is_none() {
assert_eq!(u32::from_ne_bytes(response[..4].try_into().unwrap()), params);
assert_eq!(u32::from_ne_bytes(response[4..8].try_into().unwrap()) & 0xffff, 0);
assert_eq!(
u32::from_ne_bytes(response[..4].try_into().unwrap()),
params
);
assert_eq!(
u32::from_ne_bytes(response[4..8].try_into().unwrap()) & 0xffff,
0
);
} else {
assert!(response.is_empty(), "create_immed success must not emit a failed event");
assert!(
response.is_empty(),
"create_immed success must not emit a failed event"
);
}
assert!(client.get_credentials(&state.display_handle).is_ok());
}
@@ -630,12 +729,26 @@ fn dmabuf_validation_uses_the_originating_global_without_a_desktop_frame() {
// Deferred requests receive one terminal failure on timeout or removal.
for params in [8, 9] {
request(&mut wire, 4, 1, &[params], None);
request(&mut wire, params, 1, &[0, 0, 16, 0, 0], Some(file.as_raw_fd()));
request(&mut wire, params, 2, &[4, 4, Fourcc::Argb8888 as u32, 0], None);
request(
&mut wire,
params,
1,
&[0, 0, 16, 0, 0],
Some(file.as_raw_fd()),
);
request(
&mut wire,
params,
2,
&[4, 4, Fourcc::Argb8888 as u32, 0],
None,
);
dispatch(&mut events, &mut state);
state.process_pending_dmabuf_imports();
if params == 8 {
state.dmabuf_imports.expire(Instant::now() + Duration::from_secs(5));
state
.dmabuf_imports
.expire(Instant::now() + Duration::from_secs(5));
} else {
state.dmabuf_imports.remove(b);
}
@@ -643,8 +756,14 @@ fn dmabuf_validation_uses_the_originating_global_without_a_desktop_frame() {
state.display_handle.flush_clients().unwrap();
let response = read(&mut wire);
assert_eq!(response.len(), 8);
assert_eq!(u32::from_ne_bytes(response[..4].try_into().unwrap()), params);
assert_eq!(u32::from_ne_bytes(response[4..8].try_into().unwrap()) & 0xffff, 1);
assert_eq!(
u32::from_ne_bytes(response[..4].try_into().unwrap()),
params
);
assert_eq!(
u32::from_ne_bytes(response[4..8].try_into().unwrap()) & 0xffff,
1
);
state
.dmabuf_imports
.process_with(b, None, |_| panic!("failed request survived"));
@@ -659,9 +778,9 @@ fn dmabuf_validation_uses_the_originating_global_without_a_desktop_frame() {
drop(wire);
dispatch(&mut events, &mut state);
state.process_pending_dmabuf_imports();
state
.dmabuf_imports
.process_with(b, None, |_| panic!("disconnected client's request survived"));
state.dmabuf_imports.process_with(b, None, |_| {
panic!("disconnected client's request survived")
});
}
#[test]
@@ -697,7 +816,10 @@ fn hidden_null_commit_removes_membership_and_remaps_without_workspace_switch() {
dispatch(&mut events, &mut state);
let remapped = state.windows.ids()[&window];
assert_ne!(id, remapped);
assert_eq!(state.workspaces.workspace_for_window(remapped), Some(active));
assert_eq!(
state.workspaces.workspace_for_window(remapped),
Some(active)
);
assert_eq!(state.workspaces.active_id(), active);
}
@@ -731,7 +853,10 @@ fn hidden_commits_update_client_geometry_and_release_resize_transactions() {
assert!(state.windows.transaction(&id).is_none());
assert_eq!(
state.windows.geometry(&id).unwrap().client.committed_size,
Some(ClientSize { width: 32, height: 48 })
Some(ClientSize {
width: 32,
height: 48
})
);
assert!(state.space.element_location(&window).is_none());
}
@@ -760,13 +885,23 @@ fn floating_maximize_and_restore_work_in_both_layouts() {
};
let mut next_object = 7;
for mode in [ferese_core::LayoutMode::Scrolling, ferese_core::LayoutMode::Tree] {
for mode in [
ferese_core::LayoutMode::Scrolling,
ferese_core::LayoutMode::Tree,
] {
let bounds = state.output_bounds().unwrap();
state.workspaces.set_active_layout_mode(mode, bounds).unwrap();
state
.workspaces
.set_active_layout_mode(mode, bounds)
.unwrap();
state.relayout();
state.set_window_maximized(id, true);
let maximized = maximized_rect(bounds, state.gap_config.outer);
assert_eq!(state.windows.geometry(&id).unwrap().logical, maximized, "{mode:?}");
assert_eq!(
state.windows.geometry(&id).unwrap().logical,
maximized,
"{mode:?}"
);
assert_eq!(
state.workspaces.placement(id),
Some(WindowPlacement::Floating { rect: normal })
@@ -785,10 +920,17 @@ fn floating_maximize_and_restore_work_in_both_layouts() {
smithay::wayland::GlobalData,
)
.unwrap();
let (width, height) = (maximized.width.round() as u32, maximized.height.round() as u32);
let (width, height) = (
maximized.width.round() as u32,
maximized.height.round() as u32,
);
let mut file = tempfile::tempfile().unwrap();
file.write_all(&0xff112233u32.to_ne_bytes().repeat((width * height) as usize))
.unwrap();
file.write_all(
&0xff112233u32
.to_ne_bytes()
.repeat((width * height) as usize),
)
.unwrap();
request(
&mut wire,
shm.id().protocol_id(),
@@ -813,7 +955,11 @@ fn floating_maximize_and_restore_work_in_both_layouts() {
}));
state.set_window_maximized(id, false);
assert_eq!(state.windows.geometry(&id).unwrap().logical, normal, "{mode:?}");
assert_eq!(
state.windows.geometry(&id).unwrap().logical,
normal,
"{mode:?}"
);
state.display_handle.flush_clients().unwrap();
ack_configure(&mut wire, 3);
request(&mut wire, 3, 3, &[0, 0, 64, 48], None);
@@ -904,7 +1050,10 @@ fn malformed_resize_limits_disconnect_only_the_client_and_motion_survives() {
offset += (header >> 16) as usize;
}
assert!(rejected, "invalid limits {minimum:?}/{maximum:?} were not rejected");
assert!(
rejected,
"invalid limits {minimum:?}/{maximum:?} were not rejected"
);
}
let (healthy, _wire) = window(&mut state, &mut events, 0xff112233);
@@ -926,37 +1075,47 @@ fn window_rule_minimum_size_replaces_the_client_advertised_minimum() {
request(&mut wire, 2, 6, &[], None);
dispatch(&mut events, &mut state);
assert_eq!(
state.effective_size_constraints(&window, (800, 600), (0, 0)).0,
state
.effective_size_constraints(&window, (800, 600), (0, 0))
.0,
(800, 600)
);
assert_eq!(state.window_constraints()[&id].min_width, 800.0);
assert_eq!(state.window_constraints()[&id].min_height, 600.0);
state.window_rules =
crate::config::Config::parse_source("window-rule transient=#false min-width=500 min-height=400\n")
.unwrap()
.window_rules()
.unwrap();
state.window_rules = crate::config::Config::parse_source(
"window-rule transient=#false min-width=500 min-height=400\n",
)
.unwrap()
.window_rules()
.unwrap();
assert_eq!(
state.effective_size_constraints(&window, (800, 600), (0, 0)).0,
state
.effective_size_constraints(&window, (800, 600), (0, 0))
.0,
(500, 400)
);
let constraints = state.window_constraints();
assert_eq!(constraints[&id].min_width, 500.0);
assert_eq!(constraints[&id].min_height, 400.0);
state.window_rules = crate::config::Config::parse_source("window-rule transient=#false min-height=400\n")
.unwrap()
.window_rules()
.unwrap();
state.window_rules =
crate::config::Config::parse_source("window-rule transient=#false min-height=400\n")
.unwrap()
.window_rules()
.unwrap();
assert_eq!(
state.effective_size_constraints(&window, (800, 600), (0, 0)).0,
state
.effective_size_constraints(&window, (800, 600), (0, 0))
.0,
(800, 400)
);
state.window_rules.clear();
assert_eq!(
state.effective_size_constraints(&window, (800, 600), (0, 0)).0,
state
.effective_size_constraints(&window, (800, 600), (0, 0))
.0,
(800, 600)
);
}
@@ -1116,7 +1275,10 @@ fn tree_tabs_join_switch_leave_and_hide_inactive_maximized_windows() {
let header = bar.rect;
let content = state.windows.geometry(&b).unwrap().visual.target;
assert_eq!(content.y, header.y + header.height);
let hit = Point::from((header.x + header.width * 0.25, header.y + header.height * 0.5));
let hit = Point::from((
header.x + header.width * 0.25,
header.y + header.height * 0.5,
));
assert_eq!(state.tab_at(hit), Some(a));
assert!(state.surface_under(hit).is_none());
state.cycle_tab(false);
@@ -1170,7 +1332,10 @@ fn scrolling_tabs_switch_with_directional_focus_and_drop_hidden_resize_holds() {
let position = state.workspaces.active().layout.viewport_x();
state.windows.set_transaction(
c,
crate::resize_transaction::ResizeTransaction::new(SERIAL_COUNTER.next_serial(), state.presentation_now()),
crate::resize_transaction::ResizeTransaction::new(
SERIAL_COUNTER.next_serial(),
state.presentation_now(),
),
);
state.focus_direction(Direction::Up);
assert_eq!(state.focused_window, Some(b));
@@ -1210,14 +1375,21 @@ fn tab_indicator_hover_switch_and_flash_preserve_window_geometry() {
let g = state
.tab_indicator_geometry(&bar, state.presented_window_rect(b).unwrap())
.unwrap();
let point = (g.hit.x + g.hit.width * 0.5, g.rail.y + g.segment_height * 0.5).into();
let point = (
g.hit.x + g.hit.width * 0.5,
g.rail.y + g.segment_height * 0.5,
)
.into();
assert_eq!(state.tab_at(point), Some(a));
assert!(
state.surface_under(point).is_none(),
"the rail must not deliver input to the client"
);
state.update_tab_indicator_hover(point);
assert_eq!(state.tab_indicator.hover.as_ref().unwrap().opacity.current, 1.);
assert_eq!(
state.tab_indicator.hover.as_ref().unwrap().opacity.current,
1.
);
assert_eq!(state.windows.geometry(&b).unwrap().logical, before);
let g = state
.tab_indicator_geometry(&bar, state.presented_window_rect(b).unwrap())
@@ -1232,13 +1404,22 @@ fn tab_indicator_hover_switch_and_flash_preserve_window_geometry() {
assert_eq!(state.tab_at(point), Some(a));
state.activate_managed_window(a);
assert_eq!(state.windows.geometry(&a).unwrap().logical, before);
assert!(state.tab_indicator.flash.is_none(), "pointer activation does not flash");
assert!(
state.tab_indicator.flash.is_none(),
"pointer activation does not flash"
);
state.update_tab_indicator_hover((799., 599.).into());
assert_eq!(state.tab_indicator.hover.as_ref().unwrap().opacity.current, 0.);
assert_eq!(
state.tab_indicator.hover.as_ref().unwrap().opacity.current,
0.
);
state.tab_indicator_settings.flash_duration = Duration::from_millis(1);
state.cycle_tab(false);
assert_eq!(state.focused_window, Some(b));
assert_eq!(state.tab_indicator.flash.as_ref().unwrap().opacity.current, 1.);
assert_eq!(
state.tab_indicator.flash.as_ref().unwrap().opacity.current,
1.
);
assert_eq!(state.windows.geometry(&b).unwrap().logical, before);
let deadline = Instant::now() + Duration::from_secs(1);
while state
@@ -1248,9 +1429,14 @@ fn tab_indicator_hover_switch_and_flash_preserve_window_geometry() {
.is_some_and(|f| f.opacity.target != 0.)
&& Instant::now() < deadline
{
events.dispatch(Duration::from_millis(5), &mut state).unwrap();
events
.dispatch(Duration::from_millis(5), &mut state)
.unwrap();
}
assert_eq!(state.tab_indicator.flash.as_ref().unwrap().opacity.current, 0.);
assert_eq!(
state.tab_indicator.flash.as_ref().unwrap().opacity.current,
0.
);
state.toggle_column_tabbed();
assert!(state.tab_indicator.hover.is_none());
assert!(state.tab_indicator.flash.is_none());
@@ -1369,7 +1555,10 @@ fn transient_fullscreen_exception_follows_parent_changes() {
let child_id = state.windows.ids()[&child];
state.set_window_fullscreen(parent_id, true);
change_transient_parent(&mut state, &child, Some(&parent));
assert_eq!(state.floating_above_fullscreen.get(&child_id), Some(&parent_id));
assert_eq!(
state.floating_above_fullscreen.get(&child_id),
Some(&parent_id)
);
change_transient_parent(&mut state, &child, None);
assert!(
!state.floating_above_fullscreen.contains_key(&child_id),
@@ -1390,7 +1579,10 @@ fn transient_fullscreen_exception_follows_parent_changes() {
);
state.set_window_fullscreen(other_id, true);
change_transient_parent(&mut state, &child, Some(&other));
assert_eq!(state.floating_above_fullscreen.get(&child_id), Some(&other_id));
assert_eq!(
state.floating_above_fullscreen.get(&child_id),
Some(&other_id)
);
state.remove_tiled_window(&other);
assert!(
state
+75 -17
View File
@@ -76,9 +76,14 @@ impl<W: Eq + Hash> WindowRegistry<W> {
W: Clone,
{
assert!(!self.ids.contains_key(&window), "window already registered");
assert!(!self.records.contains_key(&id), "window ID already registered");
assert!(
!self.records.contains_key(&id),
"window ID already registered"
);
let index = self.overview_order.partition_point(|existing| existing.0 < id.0);
let index = self
.overview_order
.partition_point(|existing| existing.0 < id.0);
self.overview_order.insert(index, id);
self.by_id.insert(id, window.clone());
self.ids.insert(window, id);
@@ -92,7 +97,10 @@ impl<W: Eq + Hash> WindowRegistry<W> {
/// Overview render and hit order, with the newest ID in front. Normal
/// stacking changes do not affect this independent presentation order.
pub fn overview_windows(&self) -> impl DoubleEndedIterator<Item = &W> {
self.overview_order.iter().rev().filter_map(|id| self.by_id.get(id))
self.overview_order
.iter()
.rev()
.filter_map(|id| self.by_id.get(id))
}
pub fn window(&self, id: WindowId) -> Option<&W> {
@@ -174,14 +182,21 @@ impl<W: Eq + Hash> WindowRegistry<W> {
pub fn expire_transactions(&mut self, now: std::time::Duration) {
for (id, record) in &mut self.records {
if record.resize.is_some_and(|transaction| transaction.expired(now)) {
if record
.resize
.is_some_and(|transaction| transaction.expired(now))
{
tracing::warn!(?id, "resize presentation deadline reached");
record.resize = None;
}
}
}
pub fn update<R>(&mut self, id: WindowId, update: impl FnOnce(&mut WindowRecord) -> R) -> Option<R> {
pub fn update<R>(
&mut self,
id: WindowId,
update: impl FnOnce(&mut WindowRecord) -> R,
) -> Option<R> {
self.records.get_mut(&id).map(update)
}
@@ -205,14 +220,18 @@ impl<W: Eq + Hash> WindowRegistry<W> {
pub fn take_column_width_requests(&mut self) -> HashSet<WindowId> {
self.records
.iter_mut()
.filter_map(|(id, record)| std::mem::take(&mut record.column_width_pending).then_some(*id))
.filter_map(|(id, record)| {
std::mem::take(&mut record.column_width_pending).then_some(*id)
})
.collect()
}
}
impl WindowRegistry<Window> {
pub fn register_window(&mut self, window: Window, id: WindowId) {
let surface = window.toplevel().map(|toplevel| toplevel.wl_surface().clone());
let surface = window
.toplevel()
.map(|toplevel| toplevel.wl_surface().clone());
self.register(window, id);
if let Some(surface) = surface {
@@ -228,7 +247,12 @@ impl WindowRegistry<Window> {
};
let record = self.records.get_mut(&id).unwrap();
with_states(surface, |states| {
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
let attributes = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
let app_id = attributes.app_id.as_deref().unwrap_or_default();
let title = attributes.title.as_deref().unwrap_or_default();
let changed = record.app_id != app_id || record.title != title;
@@ -292,7 +316,10 @@ mod tests {
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
for _ in 0..100 {
assert!(std::rc::Rc::ptr_eq(windows.overview_windows().next().unwrap(), &handle));
assert!(std::rc::Rc::ptr_eq(
windows.overview_windows().next().unwrap(),
&handle
));
assert_eq!(std::rc::Rc::strong_count(&handle), 3);
}
@@ -323,7 +350,11 @@ mod tests {
assert_eq!(windows.remove(&"surface"), Some(id));
assert_eq!(windows.remove(&"surface"), None);
assert!(windows.record(id).is_none());
assert!(windows.update(id, |record| record.maximized = true).is_none());
assert!(
windows
.update(id, |record| record.maximized = true)
.is_none()
);
windows.set_geometry(id, geometry);
windows.set_transaction(id, resize);
@@ -367,7 +398,10 @@ mod tests {
windows.remove(&"first");
assert_eq!(windows.take_column_width_requests(), HashSet::from([second]));
assert_eq!(
windows.take_column_width_requests(),
HashSet::from([second])
);
assert!(windows.take_column_width_requests().is_empty());
assert!(windows.record(second).is_some());
}
@@ -383,12 +417,25 @@ mod tests {
windows.set_transaction(id, ResizeTransaction::new(serial.into(), now));
windows.expire_transactions(now);
assert!(windows.transaction(&id).is_some());
assert!(!windows.transaction(&id).unwrap().accepts(Some((serial - 1).into())));
assert!(
!windows
.transaction(&id)
.unwrap()
.accepts(Some((serial - 1).into()))
);
}
windows.expire_transactions(Duration::from_millis(300));
assert!(windows.transaction(&id).is_none());
windows.set_transaction(id, ResizeTransaction::new(31.into(), Duration::from_millis(400)));
assert!(!windows.transaction(&id).unwrap().expired(Duration::from_millis(699)));
windows.set_transaction(
id,
ResizeTransaction::new(31.into(), Duration::from_millis(400)),
);
assert!(
!windows
.transaction(&id)
.unwrap()
.expired(Duration::from_millis(699))
);
windows.expire_transactions(Duration::from_millis(700));
assert!(windows.transaction(&id).is_none());
}
@@ -405,13 +452,24 @@ mod tests {
let geometry = WindowGeometry::new(Rect::new(0., 0., 800., 600.), None);
windows.set_geometry(first, geometry);
windows.set_transaction(first, ResizeTransaction::new(1.into(), Duration::ZERO));
windows.set_transaction(second, ResizeTransaction::new(2.into(), Duration::from_millis(100)));
windows.set_transaction(
second,
ResizeTransaction::new(2.into(), Duration::from_millis(100)),
);
windows.expire_transactions(Duration::from_millis(300));
assert_eq!(windows.geometry(&first), Some(&geometry));
assert!(windows.transaction(&first).is_none());
assert!(windows.transaction(&second).unwrap().accepts(Some(2.into())));
assert_eq!(windows.resizing().copied().collect::<Vec<_>>(), vec![second]);
assert!(
windows
.transaction(&second)
.unwrap()
.accepts(Some(2.into()))
);
assert_eq!(
windows.resizing().copied().collect::<Vec<_>>(),
vec![second]
);
}
}
+212 -66
View File
@@ -16,9 +16,16 @@ impl Ferese {
}
pub(crate) fn restore_initial_floating_size(&mut self, window: &Window) {
let Some(toplevel) = window.toplevel() else { return };
let Some(toplevel) = window.toplevel() else {
return;
};
let (app_id, title, transient) = with_states(toplevel.wl_surface(), |states| {
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
let attributes = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
(
attributes.app_id.clone(),
attributes.title.clone(),
@@ -29,12 +36,19 @@ impl Ferese {
pending.states.contains(xdg_toplevel::State::Fullscreen)
|| pending.states.contains(xdg_toplevel::State::Maximized)
});
let rule = resolve_window_rules(&self.window_rules, app_id.as_deref(), title.as_deref(), transient);
let rule = resolve_window_rules(
&self.window_rules,
app_id.as_deref(),
title.as_deref(),
transient,
);
if crate::window_rules::is_native_dialog(app_id.as_deref())
|| transient
|| special_mode
|| rule.fullscreen == Some(true)
|| !rule.floating.unwrap_or(rule.width.is_some() || rule.height.is_some())
|| !rule
.floating
.unwrap_or(rule.width.is_some() || rule.height.is_some())
{
return;
}
@@ -62,7 +76,8 @@ impl Ferese {
Some((rule.width, rule.height)),
);
let size = ClientSize::from_rect(rect);
toplevel.with_pending_state(|pending| pending.size = Some((size.width, size.height).into()));
toplevel
.with_pending_state(|pending| pending.size = Some((size.width, size.height).into()));
}
pub(crate) fn add_rule_placed_window(
@@ -114,16 +129,22 @@ impl Ferese {
let size_rect = client_size(&window)
.map(|size| natural_floating_rect(bounds, size))
.unwrap_or_else(|| centered_floating_rect(bounds));
let rect = self.place_floating(&window, None, bounds, (size_rect.width, size_rect.height), None);
let rect = self.place_floating(
&window,
None,
bounds,
(size_rect.width, size_rect.height),
None,
);
let anchor = self
.focused_window
.and_then(|focused| self.presented_window_rect(focused))
.unwrap_or(bounds);
let fullscreen = self.workspaces.active().fullscreen;
let focus = true;
if let Err(error) = self
.workspaces
.insert_floating_window(id, self.workspaces.active_id(), rect, focus)
if let Err(error) =
self.workspaces
.insert_floating_window(id, self.workspaces.active_id(), rect, focus)
{
tracing::error!(%error, ?id, "failed to insert rule-placed floating window");
return;
@@ -131,11 +152,16 @@ impl Ferese {
self.windows.register_window(window.clone(), id);
self.refresh_capture_privacy();
self.windows.update(id, |w| w.placement_anchor = Some(anchor));
self.windows
.update(id, |w| w.placement_anchor = Some(anchor));
if rule.width.is_none() && rule.height.is_none() && client_size(&window).is_none() && remembered_work.is_none()
if rule.width.is_none()
&& rule.height.is_none()
&& client_size(&window).is_none()
&& remembered_work.is_none()
{
self.windows.update(id, |w| w.natural_floating_pending = true);
self.windows
.update(id, |w| w.natural_floating_pending = true);
}
self.window_stack.insert(id);
@@ -202,12 +228,22 @@ impl Ferese {
let size_rect = client_size(&window)
.map(|s| natural_floating_rect(bounds, s))
.unwrap_or_else(|| centered_transient_rect(visible_parent));
let rect = self.place_floating(&window, None, bounds, (size_rect.width, size_rect.height), None);
let rect = self.place_floating(
&window,
None,
bounds,
(size_rect.width, size_rect.height),
None,
);
let id = self.windows.allocate_id();
let focus = workspace == self.workspaces.active_id()
&& (self.focused_window == Some(parent) || self.workspaces.active().fullscreen == Some(parent));
if let Err(error) = self.workspaces.insert_floating_window(id, workspace, rect, focus) {
&& (self.focused_window == Some(parent)
|| self.workspaces.active().fullscreen == Some(parent));
if let Err(error) = self
.workspaces
.insert_floating_window(id, workspace, rect, focus)
{
tracing::error!(%error, ?id, ?parent, "failed to insert transient window");
return;
}
@@ -216,7 +252,8 @@ impl Ferese {
self.refresh_capture_privacy();
self.window_stack.insert(id);
if client_size(&window).is_none() {
self.windows.update(id, |w| w.natural_floating_pending = true);
self.windows
.update(id, |w| w.natural_floating_pending = true);
}
if self
@@ -286,8 +323,10 @@ impl Ferese {
);
(
(
rule.min_width.map_or(minimum.0, |width| width.ceil() as i32),
rule.min_height.map_or(minimum.1, |height| height.ceil() as i32),
rule.min_width
.map_or(minimum.0, |width| width.ceil() as i32),
rule.min_height
.map_or(minimum.1, |height| height.ceil() as i32),
),
maximum,
)
@@ -303,15 +342,26 @@ impl Ferese {
};
let (app_id, title, transient) = with_states(toplevel.wl_surface(), |states| {
let attributes = states.data_map.get::<XdgToplevelSurfaceData>().unwrap().lock().unwrap();
let attributes = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
(
attributes.app_id.clone(),
attributes.title.clone(),
attributes.parent.is_some(),
)
});
let old = resolve_window_rules(old_rules, app_id.as_deref(), title.as_deref(), transient);
let new = resolve_window_rules(&self.window_rules, app_id.as_deref(), title.as_deref(), transient);
let old =
resolve_window_rules(old_rules, app_id.as_deref(), title.as_deref(), transient);
let new = resolve_window_rules(
&self.window_rules,
app_id.as_deref(),
title.as_deref(),
transient,
);
minimums_changed |= old.min_width != new.min_width || old.min_height != new.min_height;
if let Some(new) = crate::window_rules::live_result(old, new, transient) {
self.apply_window_rule_result(&window, new);
@@ -322,7 +372,11 @@ impl Ferese {
}
}
pub(super) fn apply_window_rule_result(&mut self, window: &Window, rule: crate::window_rules::WindowRuleResult) {
pub(super) fn apply_window_rule_result(
&mut self,
window: &Window,
rule: crate::window_rules::WindowRuleResult,
) {
let Some(id) = self.windows.ids().get(window).copied() else {
return;
};
@@ -347,8 +401,12 @@ impl Ferese {
.workspaces
.workspace_for_window(id)
.and_then(|workspace| self.output_workspaces.output_for_workspace(workspace))
&& let Some(workspace) = self.output_workspaces.workspace_at(&self.workspaces, output, index)
&& let Err(error) = self.workspaces.move_window_to_workspace(id, workspace, axis, 0.5)
&& let Some(workspace) =
self.output_workspaces
.workspace_at(&self.workspaces, output, index)
&& let Err(error) = self
.workspaces
.move_window_to_workspace(id, workspace, axis, 0.5)
{
tracing::warn!(%error, ?id, ?workspace, "failed to apply window workspace rule");
}
@@ -359,7 +417,10 @@ impl Ferese {
.floating
.or((rule.width.is_some() || rule.height.is_some()).then_some(true));
if let Some(should_float) = should_float {
let is_floating = matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }));
let is_floating = matches!(
self.workspaces.placement(id),
Some(WindowPlacement::Floating { .. })
);
let mut rect = centered_floating_rect(bounds);
if should_float && rule.width.is_none() && rule.height.is_none() {
if let Some(size) = client_size(window) {
@@ -371,7 +432,8 @@ impl Ferese {
.and_then(|entry| entry.restore(&self.floating_outputs()))
.is_none()
{
self.windows.update(id, |w| w.natural_floating_pending = true);
self.windows
.update(id, |w| w.natural_floating_pending = true);
}
}
rect.width = rule.width.unwrap_or(rect.width).min(bounds.width);
@@ -387,7 +449,9 @@ impl Ferese {
}
let result = if should_float != is_floating {
self.workspaces.toggle_floating(id, rect, axis, 0.5).map(|_| ())
self.workspaces
.toggle_floating(id, rect, axis, 0.5)
.map(|_| ())
} else if should_float && (rule.width.is_some() || rule.height.is_some()) {
self.workspaces.set_floating_rect(id, rect)
} else {
@@ -426,11 +490,18 @@ impl Ferese {
self.invalidate_window_stacking();
}
let placement = parent.and_then(|parent| {
if self.workspaces.workspace_for_window(id) != self.workspaces.workspace_for_window(parent) {
tracing::warn!(?id, ?parent, "ignored transient parent on another workspace");
if self.workspaces.workspace_for_window(id)
!= self.workspaces.workspace_for_window(parent)
{
tracing::warn!(
?id,
?parent,
"ignored transient parent on another workspace"
);
return None;
}
self.floating_bounds_for_window(parent).map(|bounds| (parent, bounds))
self.floating_bounds_for_window(parent)
.map(|bounds| (parent, bounds))
});
let Some((parent, bounds)) = placement else {
if removed {
@@ -444,7 +515,13 @@ impl Ferese {
let size_rect = client_size(window)
.map(|s| natural_floating_rect(bounds, s))
.unwrap_or_else(|| centered_transient_rect(visible_parent));
let rect = self.place_floating(window, Some(id), bounds, (size_rect.width, size_rect.height), None);
let rect = self.place_floating(
window,
Some(id),
bounds,
(size_rect.width, size_rect.height),
None,
);
let result = match self.workspaces.placement(id) {
Some(WindowPlacement::Tiled) => self
.workspaces
@@ -501,8 +578,11 @@ impl Ferese {
self.refresh_capture_privacy();
self.focus_history.remove(id);
#[cfg(feature = "resize-metrics")]
self.resize_metrics
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
self.resize_metrics.end(
id,
self.presentation_now(),
crate::resize_metrics::End::Cancelled,
);
self.render.remove_window(id);
self.capture_render.remove_window(id);
self.window_stack.remove(id);
@@ -532,7 +612,9 @@ impl Ferese {
};
let serial = window.toplevel().and_then(|toplevel| {
toplevel.with_cached_state(|state| state.last_acked.as_ref().map(|configure| configure.serial))
toplevel.with_cached_state(|state| {
state.last_acked.as_ref().map(|configure| configure.serial)
})
});
if !transaction.accepts(serial) {
return;
@@ -547,7 +629,9 @@ impl Ferese {
return;
};
let source_geometry = transaction.source_geometry().unwrap_or_else(|| window.geometry());
let source_geometry = transaction
.source_geometry()
.unwrap_or_else(|| window.geometry());
let scale = output.current_scale().fractional_scale();
let source_size = source_geometry.size.to_physical_precise_round(scale);
if self.render.snapshot(&id).is_some_and(|snapshot| {
@@ -561,8 +645,17 @@ impl Ferese {
return;
}
let used: usize = self.render.snapshots().map(|snapshot| snapshot.bytes()).sum::<usize>()
+ self.render.closing.iter().map(|window| window.bytes()).sum::<usize>();
let used: usize = self
.render
.snapshots()
.map(|snapshot| snapshot.bytes())
.sum::<usize>()
+ self
.render
.closing
.iter()
.map(|window| window.bytes())
.sum::<usize>();
let remaining = crate::presentation::SNAPSHOT_BUDGET.saturating_sub(used);
let result = if let Some(backend) = &self.nested_backend {
// Commit dispatch does not run inside the Winit event callback;
@@ -584,7 +677,11 @@ impl Ferese {
};
match result {
Ok(Some(snapshot)) => {
tracing::debug!(?id, bytes = snapshot.bytes(), "captured native resize handoff");
tracing::debug!(
?id,
bytes = snapshot.bytes(),
"captured native resize handoff"
);
self.render.set_snapshot(id, snapshot);
}
Ok(None) => {}
@@ -601,7 +698,9 @@ impl Ferese {
// Read the acknowledgement attached to this commit, not a newer ack
// that arrived without its corresponding buffer.
let committed_serial = window.toplevel().and_then(|toplevel| {
toplevel.with_cached_state(|state| state.last_acked.as_ref().map(|configure| configure.serial))
toplevel.with_cached_state(|state| {
state.last_acked.as_ref().map(|configure| configure.serial)
})
});
if self
.windows
@@ -611,8 +710,11 @@ impl Ferese {
#[cfg(feature = "resize-metrics")]
{
self.measure_resize_pauses(self.presentation_now());
self.resize_metrics
.end(id, self.presentation_now(), crate::resize_metrics::End::Commit);
self.resize_metrics.end(
id,
self.presentation_now(),
crate::resize_metrics::End::Commit,
);
}
self.windows.clear_transaction(&id);
self.release_presentation_dependencies();
@@ -643,15 +745,24 @@ impl Ferese {
.is_some_and(|workspace| workspace.fullscreen == Some(id));
let normal_geometry = !fullscreen
&& !self.windows.record(id).is_some_and(|w| w.maximized)
&& self.windows.geometry(&id).is_none_or(|geometry| !geometry.is_zooming());
&& self
.windows
.geometry(&id)
.is_none_or(|geometry| !geometry.is_zooming());
let initial_floating = self
.windows
.update(id, |w| std::mem::take(&mut w.natural_floating_pending))
.unwrap_or(false)
|| self.windows.record(id).is_some_and(|w| w.placement_anchor.is_some());
|| self
.windows
.record(id)
.is_some_and(|w| w.placement_anchor.is_some());
if initial_floating
&& normal_geometry
&& matches!(self.workspaces.placement(id), Some(WindowPlacement::Floating { .. }))
&& matches!(
self.workspaces.placement(id),
Some(WindowPlacement::Floating { .. })
)
&& let Some(bounds) = self.floating_bounds_for_window(id)
{
let rect = self.place_floating(
@@ -665,10 +776,14 @@ impl Ferese {
if self.workspaces.set_floating_rect(id, rect).is_ok() {
// This is the first mapped client buffer, not a user resize.
// Do not animate from the temporary placement box.
self.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
self.windows
.set_geometry(id, WindowGeometry::new(rect, Some(size)));
#[cfg(feature = "resize-metrics")]
self.resize_metrics
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
self.resize_metrics.end(
id,
self.presentation_now(),
crate::resize_metrics::End::Cancelled,
);
self.windows.clear_transaction(&id);
self.relayout();
}
@@ -681,17 +796,23 @@ impl Ferese {
let settled_configure = window.toplevel().is_some_and(|toplevel| {
let committed = toplevel.with_cached_state(|state| state.last_acked.clone());
with_states(toplevel.wl_surface(), |states| {
states.data_map.get::<XdgToplevelSurfaceData>().is_some_and(|data| {
let Ok(data) = data.lock() else { return false };
floating_commit_is_current(
data.pending_configures().is_empty(),
committed.as_ref().map(|configure| configure.serial),
data.last_acked.as_ref().map(|configure| configure.serial),
committed
.as_ref()
.is_some_and(|configure| configure.state.states.contains(xdg_toplevel::State::Resizing)),
)
})
states
.data_map
.get::<XdgToplevelSurfaceData>()
.is_some_and(|data| {
let Ok(data) = data.lock() else { return false };
floating_commit_is_current(
data.pending_configures().is_empty(),
committed.as_ref().map(|configure| configure.serial),
data.last_acked.as_ref().map(|configure| configure.serial),
committed.as_ref().is_some_and(|configure| {
configure
.state
.states
.contains(xdg_toplevel::State::Resizing)
}),
)
})
})
});
if settled_configure
@@ -699,25 +820,45 @@ impl Ferese {
&& let Some(WindowPlacement::Floating { rect }) = self.workspaces.placement(id)
&& ClientSize::from_rect(rect) != size
{
let mut rect = Rect::new(rect.x, rect.y, f64::from(size.width), f64::from(size.height));
if let Some((left, top, anchor)) = self.windows.record(id).and_then(|w| w.resize_anchor.as_ref()) {
rect = crate::floating::anchored_size(*anchor, (rect.width, rect.height), *left, *top);
let mut rect = Rect::new(
rect.x,
rect.y,
f64::from(size.width),
f64::from(size.height),
);
if let Some((left, top, anchor)) = self
.windows
.record(id)
.and_then(|w| w.resize_anchor.as_ref())
{
rect =
crate::floating::anchored_size(*anchor, (rect.width, rect.height), *left, *top);
} else if initial_floating && let Some(work) = self.floating_bounds_for_window(id) {
rect = crate::floating::clamp_to_work(rect, work);
}
if self.workspaces.set_floating_rect(id, rect).is_ok() {
self.windows.set_geometry(id, WindowGeometry::new(rect, Some(size)));
self.windows
.set_geometry(id, WindowGeometry::new(rect, Some(size)));
#[cfg(feature = "resize-metrics")]
self.resize_metrics
.end(id, self.presentation_now(), crate::resize_metrics::End::Cancelled);
self.resize_metrics.end(
id,
self.presentation_now(),
crate::resize_metrics::End::Cancelled,
);
self.windows.clear_transaction(&id);
self.render.clear_snapshot(&id);
self.relayout();
}
}
if settled_configure && self.windows.update(id, |w| w.resize_anchor.take()).flatten().is_some() {
if settled_configure
&& self
.windows
.update(id, |w| w.resize_anchor.take())
.flatten()
.is_some()
{
self.remember_floating(window);
}
@@ -726,7 +867,12 @@ impl Ferese {
};
let matches_target = geometry.client.commit(size);
tracing::debug!(?id, ?size, matches_target, "recorded client geometry commit");
tracing::debug!(
?id,
?size,
matches_target,
"recorded client geometry commit"
);
}
pub fn close_focused_window(&mut self) {