Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
This commit is contained in:
1 parent
a2be421974
commit
993bc0f88f
391 files changed
+130908
-76
No files matched your search
@@ -0,0 +1,182 @@
|
||||
//! Compare damaged swapchain frames with an independently repainted scene.
|
||||
use super::*;
|
||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||
use smithay::backend::renderer::element::solid::SolidColorBuffer;
|
||||
use smithay::utils::user_data::UserDataMap;
|
||||
|
||||
fn blur(
|
||||
renderer: &mut GlesRenderer,
|
||||
program: &BlurProgram,
|
||||
scale: f64,
|
||||
size: Size<i32, Physical>,
|
||||
transform: Transform,
|
||||
) -> BlurRenderElement {
|
||||
let sample = Rectangle::<i32, Logical>::new((0, 10).into(), (145, 80).into());
|
||||
let visible = Rectangle::<i32, Logical>::new((10, 20).into(), (120, 55).into());
|
||||
let sample_physical = sample.to_physical_precise_round(scale);
|
||||
let framebuffer = framebuffer_clip_rect(sample_physical, size, transform.invert());
|
||||
let capture = framebuffer_capture_rect(framebuffer);
|
||||
let texture = renderer
|
||||
.create_buffer(Fourcc::Abgr8888, (capture[2], capture[3]).into())
|
||||
.unwrap();
|
||||
BlurRenderElement::new(
|
||||
texture,
|
||||
program.0.clone(),
|
||||
&MaterialParameters {
|
||||
panel: None,
|
||||
corner_radii: [0.; 4],
|
||||
presentation_alpha: 1.,
|
||||
background_opacity: 0.,
|
||||
blur: (8. * scale) as f32,
|
||||
sample_geometry: sample,
|
||||
sample_physical,
|
||||
sample_framebuffer: framebuffer,
|
||||
shadow_rect: [0.; 4],
|
||||
shadow_values: [0.; 2],
|
||||
shadow_bounds: visible,
|
||||
geometry: visible,
|
||||
tint: [0.; 4],
|
||||
generation: 0,
|
||||
opaque: false,
|
||||
visible_framebuffer: framebuffer_clip_rect(
|
||||
visible.to_physical_precise_round(scale),
|
||||
size,
|
||||
transform.invert(),
|
||||
),
|
||||
},
|
||||
Arc::new(AtomicBool::new(true)),
|
||||
)
|
||||
}
|
||||
|
||||
fn pixels(renderer: &mut GlesRenderer, texture: &mut GlesTexture) -> Vec<u8> {
|
||||
let size = texture.size();
|
||||
let target = renderer.bind(texture).unwrap();
|
||||
let mapping = renderer
|
||||
.copy_framebuffer(&target, Rectangle::from_size(size), Fourcc::Abgr8888)
|
||||
.unwrap();
|
||||
renderer.map_texture(&mapping).unwrap().to_vec()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires an EGL rendering device"]
|
||||
fn blur_behind_moving_opaque_window_matches_full_frames_at_buffer_age_three() {
|
||||
let mut renderer = {
|
||||
use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
|
||||
let software = std::env::var_os("FERESE_TEST_EGL_SOFTWARE").is_some();
|
||||
let device = EGLDevice::enumerate()
|
||||
.unwrap()
|
||||
.find(|device| device.is_software() == software)
|
||||
.unwrap();
|
||||
// SAFETY: The enumerated device remains owned by the EGL display/context.
|
||||
let display = unsafe { EGLDisplay::new(device).unwrap() };
|
||||
// SAFETY: This test owns the renderer's fresh context exclusively.
|
||||
unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() }
|
||||
};
|
||||
let mut resources = RenderResources::default();
|
||||
let program = blur_program(&mut resources, &mut renderer).unwrap();
|
||||
for scale in [1., 1.5, 2.] {
|
||||
for transform in [Transform::Normal, Transform::Flipped180] {
|
||||
let size: Size<i32, Physical> = ((160. * scale) as i32, (100. * scale) as i32).into();
|
||||
let mut tracker = OutputDamageTracker::new(size, scale, transform);
|
||||
let mut targets = (0..3)
|
||||
.map(|_| {
|
||||
renderer
|
||||
.create_buffer(Fourcc::Abgr8888, (size.w, size.h).into())
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<GlesTexture>>();
|
||||
let mut reference: GlesTexture = renderer
|
||||
.create_buffer(Fourcc::Abgr8888, (size.w, size.h).into())
|
||||
.unwrap();
|
||||
let effect = blur(&mut renderer, &program, scale, size, transform);
|
||||
let mut background = SolidColorBuffer::new((160, 100), [0.1, 0.7, 0.2, 1.]);
|
||||
let foreground = SolidColorBuffer::new((60, 90), [0.2, 0.1, 0.8, 1.]);
|
||||
for frame_index in 0..9 {
|
||||
if frame_index % 3 == 0 {
|
||||
background.set_color([0.1, 0.3 + frame_index as f32 * 0.05, 0.2, 1.]);
|
||||
}
|
||||
let solid = || {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&background,
|
||||
(0, 0),
|
||||
scale,
|
||||
1.,
|
||||
RenderElementKind::Unspecified,
|
||||
)
|
||||
};
|
||||
let front = SolidColorRenderElement::from_buffer(
|
||||
&foreground,
|
||||
(((100 - frame_index * 3) as f64 * scale) as i32, 0),
|
||||
scale,
|
||||
1.,
|
||||
RenderElementKind::Unspecified,
|
||||
);
|
||||
let elements: Vec<AnimatedWindowRenderElement> =
|
||||
vec![front.clone().into(), effect.clone().into(), solid().into()];
|
||||
backdrop::update(&elements, scale);
|
||||
let slot = frame_index as usize % 3;
|
||||
{
|
||||
let mut target = renderer.bind(&mut targets[slot]).unwrap();
|
||||
tracker
|
||||
.render_output(
|
||||
&mut renderer,
|
||||
&mut target,
|
||||
if frame_index < 3 { 0 } else { 3 },
|
||||
&elements,
|
||||
Color32F::TRANSPARENT,
|
||||
)
|
||||
.unwrap()
|
||||
.sync
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Paint every layer without opaque culling or damage reuse.
|
||||
// A separate capture texture prevents the oracle from repairing
|
||||
// the incremental renderer's cached copy as a side effect.
|
||||
let reference_blur = blur(&mut renderer, &program, scale, size, transform);
|
||||
let full_elements: Vec<AnimatedWindowRenderElement> =
|
||||
vec![front.into(), reference_blur.into(), solid().into()];
|
||||
{
|
||||
let mut target = renderer.bind(&mut reference).unwrap();
|
||||
let mut frame = renderer
|
||||
.render(&mut target, size, transform.invert())
|
||||
.unwrap();
|
||||
let full = Rectangle::from_size(size);
|
||||
frame.clear(Color32F::TRANSPARENT, &[full]).unwrap();
|
||||
for element in full_elements.iter().rev() {
|
||||
let cache = UserDataMap::default();
|
||||
let rect = element.geometry(scale.into());
|
||||
if element.is_framebuffer_effect() {
|
||||
element
|
||||
.capture_framebuffer(&mut frame, element.src(), rect, &cache)
|
||||
.unwrap();
|
||||
}
|
||||
element
|
||||
.draw(
|
||||
&mut frame,
|
||||
element.src(),
|
||||
rect,
|
||||
&[Rectangle::from_size(rect.size)],
|
||||
&[],
|
||||
element.is_framebuffer_effect().then_some(&cache),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
frame.finish().unwrap().wait().unwrap();
|
||||
}
|
||||
let actual = pixels(&mut renderer, &mut targets[slot]);
|
||||
let expected = pixels(&mut renderer, &mut reference);
|
||||
let differing = actual
|
||||
.iter()
|
||||
.zip(&expected)
|
||||
.filter(|(a, b)| a.abs_diff(**b) > 1)
|
||||
.count();
|
||||
assert_eq!(
|
||||
differing, 0,
|
||||
"scale={scale} transform={transform:?} frame={frame_index}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ pub(crate) fn capture_resize_snapshot(
|
||||
) -> Result<Option<ResizeSnapshot>, GlesError> {
|
||||
// Called before on_commit_buffer_handler: renderer surface state still
|
||||
// owns the previous buffer during an actual resize handoff.
|
||||
render_window_texture(renderer, window, geometry, scale, remaining, None)
|
||||
render_window_texture(renderer, window, geometry, scale, remaining, None, true)
|
||||
}
|
||||
|
||||
fn render_window_texture(
|
||||
@@ -28,6 +28,7 @@ fn render_window_texture(
|
||||
scale: f64,
|
||||
remaining: usize,
|
||||
reusable: Option<GlesTexture>,
|
||||
before_source_release: bool,
|
||||
) -> Result<Option<ResizeSnapshot>, GlesError> {
|
||||
let Some(toplevel) = window.toplevel() else {
|
||||
return Ok(None);
|
||||
@@ -68,7 +69,13 @@ fn render_window_texture(
|
||||
let damage = Rectangle::from_size(size);
|
||||
frame.clear(Color32F::new(0.0, 0.0, 0.0, 0.0), &[damage])?;
|
||||
draw_render_elements(&mut frame, scale, &content, &[damage])?;
|
||||
let _ = frame.finish()?;
|
||||
let copied = frame.finish()?;
|
||||
if before_source_release {
|
||||
// This function runs before the old wl_buffer is replaced. The
|
||||
// client may immediately reuse it after release, so the detached
|
||||
// animation image must be complete before that release is possible.
|
||||
copied.wait().map_err(|_| GlesError::SyncInterrupted)?;
|
||||
}
|
||||
}
|
||||
Ok(Some(ResizeSnapshot {
|
||||
texture,
|
||||
@@ -118,6 +125,7 @@ pub(crate) fn capture_window_frame(
|
||||
scale,
|
||||
128 * 1024 * 1024,
|
||||
reusable,
|
||||
false,
|
||||
)
|
||||
.map_err(|error| error.to_string())?
|
||||
.ok_or("Window has no capturable content")?;
|
||||
@@ -422,6 +430,7 @@ pub(crate) fn capture_stream_window(
|
||||
info.scale,
|
||||
128 * 1024 * 1024,
|
||||
reusable,
|
||||
false,
|
||||
)
|
||||
.map_err(|error| error.to_string())?
|
||||
.ok_or("Window has no capturable content")?;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
//! Real input-method protocol resources and SHM bounds; no host GUI is used.
|
||||
use super::layer_popup_geometry;
|
||||
use crate::Ferese;
|
||||
use crate::state::{dispatch_test_client as dispatch, test_client_request as request, test_window};
|
||||
use smithay::desktop::PopupKind;
|
||||
use smithay::reexports::calloop::EventLoop;
|
||||
use smithay::reexports::wayland_protocols_misc::zwp_input_method_v2::server::zwp_input_method_manager_v2::ZwpInputMethodManagerV2;
|
||||
use smithay::reexports::wayland_server::protocol::{
|
||||
wl_compositor::WlCompositor, wl_shm::WlShm, wl_surface::WlSurface,
|
||||
};
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::utils::{Physical, Point, Rectangle};
|
||||
use smithay::wayland::input_method::PopupParent;
|
||||
use smithay::wayland::text_input::TextInputSeat;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
fn bind_seat(state: &mut Ferese, events: &mut EventLoop<'static, Ferese>, wire: &mut UnixStream) {
|
||||
request(wire, 1, 1, &[7], None);
|
||||
dispatch(events, state);
|
||||
wire.set_nonblocking(true).unwrap();
|
||||
let mut bytes = Vec::new();
|
||||
let _ = wire.read_to_end(&mut bytes);
|
||||
wire.set_nonblocking(false).unwrap();
|
||||
let mut offset = 0;
|
||||
let mut seat = None;
|
||||
while offset < bytes.len() {
|
||||
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 == 7 && header as u16 == 0 {
|
||||
let length =
|
||||
u32::from_ne_bytes(bytes[offset + 12..offset + 16].try_into().unwrap()) as usize;
|
||||
if &bytes[offset + 16..offset + 16 + length] == b"wl_seat\0" {
|
||||
seat = Some(u32::from_ne_bytes(
|
||||
bytes[offset + 8..offset + 12].try_into().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
offset += (header >> 16) as usize;
|
||||
}
|
||||
let mut args = vec![seat.unwrap(), 8];
|
||||
args.extend(
|
||||
b"wl_seat\0"
|
||||
.chunks_exact(4)
|
||||
.map(|word| u32::from_ne_bytes(word.try_into().unwrap())),
|
||||
);
|
||||
args.extend([7, 8]);
|
||||
request(wire, 7, 0, &args, None);
|
||||
dispatch(events, state);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_method_popup_preserves_layer_origin_and_uses_own_surface_bounds() {
|
||||
if !crate::startup_tests::private_runtime(
|
||||
"render::scene::layer_popup_tests::input_method_popup_preserves_layer_origin_and_uses_own_surface_bounds",
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let mut events = EventLoop::try_new().unwrap();
|
||||
let mut state = crate::startup_tests::state(&mut events);
|
||||
let (window, mut wire) = test_window(&mut state, &mut events, 0xff102030);
|
||||
let parent = window.toplevel().unwrap().wl_surface().clone();
|
||||
let client = parent.client().unwrap();
|
||||
state.seat.text_input().set_focus(Some(parent.clone()));
|
||||
bind_seat(&mut state, &mut events, &mut wire);
|
||||
let manager = client
|
||||
.create_resource::<ZwpInputMethodManagerV2, smithay::wayland::GlobalData, Ferese>(
|
||||
&state.display_handle,
|
||||
1,
|
||||
smithay::wayland::GlobalData,
|
||||
)
|
||||
.unwrap();
|
||||
let compositor = client
|
||||
.create_resource::<WlCompositor, smithay::wayland::GlobalData, Ferese>(
|
||||
&state.display_handle,
|
||||
6,
|
||||
smithay::wayland::GlobalData,
|
||||
)
|
||||
.unwrap();
|
||||
let shm = client
|
||||
.create_resource::<WlShm, smithay::wayland::GlobalData, Ferese>(
|
||||
&state.display_handle,
|
||||
1,
|
||||
smithay::wayland::GlobalData,
|
||||
)
|
||||
.unwrap();
|
||||
request(&mut wire, manager.id().protocol_id(), 0, &[8, 9], None);
|
||||
request(&mut wire, compositor.id().protocol_id(), 0, &[10], None);
|
||||
request(&mut wire, 9, 4, &[11, 10], None); // get_input_popup_surface
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
file.write_all(&0xff204060u32.to_ne_bytes().repeat(364 * 80))
|
||||
.unwrap();
|
||||
request(
|
||||
&mut wire,
|
||||
shm.id().protocol_id(),
|
||||
0,
|
||||
&[12, 364 * 80 * 4],
|
||||
Some(file.as_raw_fd()),
|
||||
);
|
||||
request(&mut wire, 12, 0, &[13, 0, 364, 80, 364 * 4, 0], None);
|
||||
request(&mut wire, 10, 1, &[13, 0, 0], None);
|
||||
request(&mut wire, 10, 6, &[], None);
|
||||
dispatch(&mut events, &mut state);
|
||||
let surface = client
|
||||
.object_from_protocol_id::<WlSurface>(&state.display_handle, 10)
|
||||
.unwrap();
|
||||
let Some(PopupKind::InputMethod(mut popup)) = state.popups.find_popup(&surface) else {
|
||||
panic!("input-method popup must be tracked");
|
||||
};
|
||||
|
||||
// Parent geometry is global, but the layer renderer receives output-local
|
||||
// coordinates. Cover a primary output and a secondary output left/above it.
|
||||
for output_origin in [(0, 0), (-1920, -300)] {
|
||||
let layer_origin = Point::from((360, 241));
|
||||
let global_parent =
|
||||
Rectangle::new(layer_origin + Point::from(output_origin), (560, 70).into());
|
||||
popup.set_parent(Some(PopupParent {
|
||||
surface: parent.clone(),
|
||||
location: global_parent,
|
||||
}));
|
||||
popup.set_location((148, 55).into());
|
||||
let kind = PopupKind::InputMethod(popup.clone());
|
||||
assert_eq!(kind.geometry(), global_parent);
|
||||
let (bounds, content_origin) = layer_popup_geometry(layer_origin, popup.location(), &kind);
|
||||
assert_eq!(content_origin, Point::from((508, 296)));
|
||||
assert_eq!(
|
||||
bounds,
|
||||
Rectangle::new((508, 296).into(), (364, 80).into()),
|
||||
"candidate bounds must come from its own buffer, not its parent"
|
||||
);
|
||||
for scale in [1.0, 1.5, 2.0] {
|
||||
let pixels: Rectangle<i32, Physical> = bounds.to_physical_precise_round(scale);
|
||||
assert_eq!(pixels.loc, content_origin.to_physical_precise_round(scale));
|
||||
assert_eq!(pixels.size.h, (80.0 * scale) as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
-24
@@ -1,4 +1,6 @@
|
||||
mod backdrop;
|
||||
#[cfg(test)]
|
||||
mod backdrop_gpu_tests;
|
||||
mod capture;
|
||||
mod closing;
|
||||
mod window_blur;
|
||||
@@ -272,9 +274,25 @@ impl Element for BlurRenderElement {
|
||||
fn kind(&self) -> RenderElementKind {
|
||||
RenderElementKind::Unspecified
|
||||
}
|
||||
|
||||
fn is_framebuffer_effect(&self) -> bool {
|
||||
// The complete sampling region must be repainted before capture,
|
||||
// including pixels hidden by opaque elements above this blur.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
fn capture_framebuffer(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_, '_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
_dst: Rectangle<i32, Physical>,
|
||||
_cache: &smithay::utils::user_data::UserDataMap,
|
||||
) -> Result<(), GlesError> {
|
||||
self.capture(frame)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_, '_>,
|
||||
@@ -282,14 +300,37 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
_cache: Option<&smithay::utils::user_data::UserDataMap>,
|
||||
cache: Option<&smithay::utils::user_data::UserDataMap>,
|
||||
) -> Result<(), GlesError> {
|
||||
// Simple full-frame renderers do not implement the effect callback.
|
||||
// Damage-tracked outputs must use its callback, never the dirty flag:
|
||||
// the tracker knows when occlusion or buffer restoration needs a copy.
|
||||
if cache.is_none() && self.capture_dirty.load(Ordering::Relaxed) {
|
||||
self.capture(frame)?;
|
||||
}
|
||||
frame.render_texture_from_to(
|
||||
&self.texture,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
Transform::Normal,
|
||||
1.0,
|
||||
Some(&self.program),
|
||||
&self.uniforms,
|
||||
)
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl BlurRenderElement {
|
||||
fn capture(&self, frame: &mut GlesFrame<'_, '_>) -> Result<(), GlesError> {
|
||||
let texture = self.texture.tex_id();
|
||||
let [x, y, width, height] = self.capture_rect;
|
||||
// Buffer-age restoration and unrelated partial damage must reuse the
|
||||
// clean captured scene. Recapturing a partly repainted framebuffer
|
||||
// would feed previous blurred pixels back into the blur.
|
||||
if self.capture_dirty.load(Ordering::Relaxed) {
|
||||
{
|
||||
// SAFETY: with_context makes this frame context current. GL writes into live local
|
||||
// scalars, and the capture texture belongs to this renderer with the allocated
|
||||
// framebuffer bounds.
|
||||
@@ -341,22 +382,7 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
}
|
||||
self.capture_dirty.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
frame.render_texture_from_to(
|
||||
&self.texture,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
Transform::Normal,
|
||||
1.0,
|
||||
Some(&self.program),
|
||||
&self.uniforms,
|
||||
)
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +564,47 @@ fn clipped_surface_damage(
|
||||
}
|
||||
}
|
||||
|
||||
fn sampled_surface_damage(
|
||||
size: Size<i32, Physical>,
|
||||
source: Size<f64, Buffer>,
|
||||
transform: Transform,
|
||||
clip_changed: bool,
|
||||
client_damage: impl FnOnce() -> DamageSet<i32, Physical>,
|
||||
) -> DamageSet<i32, Physical> {
|
||||
clipped_surface_damage(size, clip_changed, || {
|
||||
// Linear filtering also changes destination samples just outside the
|
||||
// changed texels. Smithay maps their rectangle to physical coordinates,
|
||||
// but does not include this half-texel sampling footprint. Expanding in
|
||||
// physical pixels prevents stale fringes on fractional-scale redraws.
|
||||
let source = transform.transform_size(source);
|
||||
let footprint = |pixels: i32, texels: f64| {
|
||||
if texels > 0.0 {
|
||||
(f64::from(pixels) / texels * 0.5).ceil() as i32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
let x = footprint(size.w, source.w);
|
||||
let y = footprint(size.h, source.h);
|
||||
let bounds = Rectangle::from_size(size);
|
||||
client_damage()
|
||||
.into_iter()
|
||||
.filter(|rect| !rect.is_empty())
|
||||
.filter_map(|rect| {
|
||||
Rectangle::new(
|
||||
(rect.loc.x.saturating_sub(x), rect.loc.y.saturating_sub(y)).into(),
|
||||
(
|
||||
rect.size.w.saturating_add(x.saturating_mul(2)),
|
||||
rect.size.h.saturating_add(y.saturating_mul(2)),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.intersection(bounds)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
impl Element for RoundedSurfaceRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
self.inner.id()
|
||||
@@ -564,9 +631,13 @@ impl Element for RoundedSurfaceRenderElement {
|
||||
scale: RenderScale<f64>,
|
||||
commit: Option<CommitCounter>,
|
||||
) -> DamageSet<i32, Physical> {
|
||||
clipped_surface_damage(self.inner.geometry(scale).size, self.clip_changed, || {
|
||||
self.inner.damage_since(scale, commit)
|
||||
})
|
||||
sampled_surface_damage(
|
||||
self.inner.geometry(scale).size,
|
||||
self.inner.src().size,
|
||||
self.inner.transform(),
|
||||
self.clip_changed,
|
||||
|| self.inner.damage_since(scale, commit),
|
||||
)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: RenderScale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
@@ -688,6 +759,39 @@ mod tests {
|
||||
shadow_bounds,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn filtered_surface_damage_stays_local_and_preserves_full_clip_changes() {
|
||||
use smithay::backend::renderer::utils::DamageSet;
|
||||
let size = (120, 80).into();
|
||||
let source = (80.0, 40.0).into();
|
||||
for (transform, x_padding) in [(Transform::Normal, 1), (Transform::_90, 2)] {
|
||||
let damage = super::sampled_surface_damage(size, source, transform, false, || {
|
||||
DamageSet::from_slice(&[
|
||||
Rectangle::new((0, 0).into(), (4, 3).into()),
|
||||
Rectangle::new((118, 78).into(), (2, 2).into()),
|
||||
])
|
||||
});
|
||||
assert_eq!(
|
||||
damage.iter().copied().collect::<Vec<_>>(),
|
||||
vec![
|
||||
Rectangle::new((0, 0).into(), (4 + x_padding, 4).into()),
|
||||
Rectangle::new((118 - x_padding, 77).into(), (2 + x_padding, 3).into(),),
|
||||
],
|
||||
);
|
||||
assert!(
|
||||
super::sampled_surface_damage(size, source, transform, false, DamageSet::default)
|
||||
.is_empty()
|
||||
);
|
||||
let full = super::sampled_surface_damage(size, source, transform, true, || {
|
||||
panic!("a changed clip must keep full damage without consulting the client")
|
||||
});
|
||||
assert_eq!(
|
||||
full.iter().copied().collect::<Vec<_>>(),
|
||||
vec![Rectangle::from_size(size)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires an EGL rendering device"]
|
||||
fn solid_corner_mask_preserves_premultiplied_color() {
|
||||
|
||||
@@ -556,9 +556,7 @@ pub(super) fn layer_elements(
|
||||
let mut elements = Vec::new();
|
||||
for (geometry, layer) in layers {
|
||||
for (popup, offset) in PopupManager::popups_for_surface(layer.wl_surface()) {
|
||||
let mut bounds = popup.geometry();
|
||||
let content_origin = geometry.loc + offset - bounds.loc;
|
||||
bounds.loc = geometry.loc + offset;
|
||||
let (bounds, content_origin) = layer_popup_geometry(geometry.loc, offset, &popup);
|
||||
append_material_surface(
|
||||
state,
|
||||
renderer,
|
||||
@@ -587,6 +585,27 @@ pub(super) fn layer_elements(
|
||||
elements
|
||||
}
|
||||
|
||||
fn layer_popup_geometry(
|
||||
layer_origin: Point<i32, Logical>,
|
||||
offset: Point<i32, Logical>,
|
||||
popup: &smithay::desktop::PopupKind,
|
||||
) -> (Rectangle<i32, Logical>, Point<i32, Logical>) {
|
||||
if matches!(popup, smithay::desktop::PopupKind::InputMethod(_)) {
|
||||
// IME geometry describes its parent in global coordinates, unlike
|
||||
// xdg window geometry. Its location is already parent-relative, and
|
||||
// its candidate surface can extend beyond the parent's bounds.
|
||||
let origin = layer_origin + offset;
|
||||
return (
|
||||
smithay::desktop::utils::bbox_from_surface_tree(popup.wl_surface(), origin),
|
||||
origin,
|
||||
);
|
||||
}
|
||||
let mut bounds = popup.geometry();
|
||||
let origin = layer_origin + offset - bounds.loc;
|
||||
bounds.loc = layer_origin + offset;
|
||||
(bounds, origin)
|
||||
}
|
||||
|
||||
fn layer_output_crop(size: Size<i32, Physical>, transform: Transform) -> Rectangle<i32, Physical> {
|
||||
Rectangle::from_size(transform.transform_size(size))
|
||||
}
|
||||
@@ -675,6 +694,10 @@ pub(super) fn cursor_elements(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "layer_popup_tests.rs"]
|
||||
mod layer_popup_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod layer_crop_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in new issue
Block a user