Format Rust sources at 100 columns
This commit is contained in:
1 parent
4661f595f8
commit
610478f8c7
336 files changed
+23497
-7892
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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.),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user