use smithay::desktop::Window; use super::*; pub(crate) fn animated_window_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, ) -> Vec { output_elements(state, renderer, output, true) } pub(crate) fn frame_effect_metrics(_elements: &[AnimatedWindowRenderElement], _scale: f64) -> FrameEffectMetrics { FrameEffectMetrics } pub(crate) fn output_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, include_cursor: bool, ) -> Vec { let frame = state.sample_frame(output, Duration::ZERO); sampled_output_elements(state, renderer, output, include_cursor, &frame) } pub(crate) fn sampled_output_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, include_cursor: bool, frame: &crate::state::FrameScene, ) -> Vec { scene_elements(state, renderer, output, include_cursor, frame, false) } pub(crate) fn capture_output_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, include_cursor: bool, frame: &crate::state::FrameScene, ) -> Vec { // Blur textures and resize handoff snapshots must never be shared with // display rendering. Only the capture scene can populate these caches. std::mem::swap(&mut state.render, &mut state.capture_render); let elements = scene_elements(state, renderer, output, include_cursor, frame, true); std::mem::swap(&mut state.render, &mut state.capture_render); elements } fn scene_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, include_cursor: bool, frame: &crate::state::FrameScene, capture: bool, ) -> Vec { if state.session_lock.active() { let Some(geometry) = state.space.output_geometry(output) else { return Vec::new(); }; let scale = output.current_scale().fractional_scale(); let background = state.session_lock.backgrounds.entry(output.clone()).or_insert_with(|| { smithay::backend::renderer::element::solid::SolidColorBuffer::new(geometry.size, [0.0, 0.0, 0.0, 1.0]) }); background.resize(geometry.size); let mut elements = Vec::new(); let opacity = state.session_lock.idle_opacity; if opacity > 0.0 { let overlay = state .session_lock .idle_overlays .entry(output.clone()) .or_insert_with(|| { smithay::backend::renderer::element::solid::SolidColorBuffer::new( geometry.size, [0.0, 0.0, 0.0, 1.0], ) }); overlay.resize(geometry.size); elements.push( SolidColorRenderElement::from_buffer(overlay, (0, 0), scale, opacity, RenderElementKind::Unspecified) .into(), ); } if let Some(surface) = state .session_lock .surfaces .get(output) .filter(|surface| surface.alive()) { elements.extend( render_elements_from_surface_tree::>( renderer, surface.wl_surface(), (0, 0), scale, 1.0, RenderElementKind::Unspecified, ) .into_iter() .map(AnimatedWindowRenderElement::from), ); } elements.push( SolidColorRenderElement::from_buffer(background, (0, 0), scale, 1.0, RenderElementKind::Unspecified).into(), ); return elements; } let Some(output_geometry) = state.space.output_geometry(output) else { return Vec::new(); }; let scale = output.current_scale().fractional_scale(); let protected_cursor = capture && state.capture_cursor_protected(); let mut elements = if include_cursor && !protected_cursor { cursor_elements(state, renderer, output_geometry, scale) } else { Vec::new() }; let upper_layers: &[Layer] = if state.output_has_fullscreen_for_frame(output, frame.overview.is_presenting()) { &[Layer::Overlay] } else { &[Layer::Overlay, Layer::Top] }; elements.extend(layer_elements(state, renderer, output, upper_layers)); if frame.overview.is_presenting() { elements.extend(overview_strip_elements( state, renderer, output, output_geometry, scale, frame, capture, )); } let prepare_window = |window: &Window| { // Configure immediately, but present the frame/shadow only after // the first buffer commit has settled the actual client geometry. if (capture && state.capture_protected(window)) || !state.window_content_ready(window) { return None; } let id = *state.windows.ids().get(window)?; // Scrolling columns may sit outside their monitor's rectangle. // They must not reappear on a neighboring output just because // their global animated coordinates overlap it. if !state.window_belongs_to_output(id, output) { return None; } let sample = frame.windows.get(&id)?; let visual = sample.presentation.bounds.current; let presentation_alpha = sample.presentation.alpha(); let decoration_progress = sample.geometry.decorations.clamp(0.0, 1.0); Some(( window.clone(), id, sample, visual, decoration_progress, presentation_alpha, )) }; let windows = if state.overview.is_active() { state .windows .overview_windows() .filter_map(prepare_window) .collect::>() } else { state .space .elements() .rev() .filter_map(prepare_window) .collect::>() }; let mut closing = super::closing::grouped_elements( state, renderer, output, windows.iter().map(|(_, id, ..)| *id), frame.delta, ); for (window, id, sample, visual, decoration_progress, presentation_alpha) in windows { if let Some(group) = closing.remove(&Some(id)) { elements.extend(group); } let constrain = rounded_visual_rect(visual, output_geometry.loc); let material_surface = window .toplevel() .map(|toplevel| toplevel.wl_surface()) .filter(|surface| { crate::effects::surface_role(surface) .is_some_and(|(role, _)| role == crate::effects::SemanticRole::Modal) }); let window_radius = if material_surface.is_some() { state.theme_settings.material_radius } else { state.theme_settings.window_radius } * decoration_progress; let shape = window_corner_shape(&window); let corners = RoundedRect::new(visual, output_geometry.loc, scale, window_radius).with_shape(shape); let corner_shape_changed = state.render.prepare_window_corners(id, shape); let rounded_clip_program = corner_program(&mut state.render, renderer, shape); // Only overview/close intentionally scale the complete application. let scale_content = sample.presentation.scale_content; let behavior = resize_content_behavior(scale_content); let dim = sample.presentation.dim; if let Some(overlay) = window_tint_element( &mut state.render, renderer, id, constrain, corners, [0.0, 0.0, 0.0, dim as f32 * presentation_alpha], false, output, ) { // Front-to-back: dim the application and its border, not its shadow // or other windows/layer surfaces. The overlay is input-transparent. elements.push(overlay.into()); } if let Some(programs) = rounded_clip_program.clone() { let shadow_offset_y = state.theme_settings.shadow_offset_y; let shadow_blur = state.theme_settings.shadow_blur; let shadow_opacity = state.theme_settings.shadow_opacity; let shadow_color = state.theme_settings.shadow_color.0; let focus = sample.presentation.focus(); let border_width = state.theme_settings.border_width + (state.theme_settings.focus_ring_width - state.theme_settings.border_width) * focus; let border_color = state.theme_settings.border_color.0; let gradient = state.theme_settings.border_gradient; if let Some(border) = window_border_element( &mut state.render, &state.theme_settings, renderer, id, constrain, corners, scale, border_width, border_color, gradient, focus as f32, presentation_alpha * decoration_progress as f32, output, &programs, ) { elements.push(border.into()); } let (offset_factor, blur_factor, opacity_factor) = sample.presentation.shadow_factors(); let shadow = window_shadow_element( &mut state.render, renderer, id, constrain, corners, scale, shadow_offset_y * offset_factor, shadow_blur * blur_factor, shadow_opacity * opacity_factor * f64::from(presentation_alpha) * decoration_progress, shadow_color, output, &programs, ); if let Some(snapshot) = state.render.snapshot(&id) && snapshot.context == renderer.context_id().erased() && (snapshot.scale - scale).abs() < 0.001 && let Some(element) = super::window_content::snapshot_element( snapshot, sample.presentation, corners, scale, output, &programs, crate::presentation::handoff_alpha(snapshot.elapsed), ) { elements.push(element.into()); } elements.extend(rounded_window_elements( renderer, &window, corners, scale, presentation_alpha, sample.geometry.presentation_changed || corner_shape_changed, output, programs.clone(), behavior, sample.presentation.native_size, )); let source = window.geometry().size; if material_surface.is_none() && !scale_content && (source.w < constrain.size.w || source.h < constrain.size.h) { let mut color = state.theme_settings.surface_base_color.0; color[3] = presentation_alpha; if let Some(fill) = window_tint_element(&mut state.render, renderer, id, constrain, corners, color, true, output) { // Front-to-back: fill uncovered strips behind the native // content instead of stretching it or exposing wallpaper. elements.push(fill.into()); } } else { state.render.clear_tint(id, true); } if let Some(surface) = material_surface && let Some((background, _)) = material_element( state, renderer, output, surface, MaterialSurface { geometry: constrain, corners, index: 0, capture_geometry: constrain, alpha: presentation_alpha, }, ) { elements.push(background); } if let Some(shadow) = shadow { elements.push(shadow.into()); } } else { elements.extend(constrain_space_element::( renderer, &window, constrain.loc, presentation_alpha, scale, constrain, ConstrainBehavior { reference: ConstrainReference::Geometry, behavior, align: ConstrainAlign::TOP | ConstrainAlign::LEFT, }, )); } } if let Some(group) = closing.remove(&None) { elements.extend(group); } elements.extend(layer_elements( state, renderer, output, &[Layer::Bottom, Layer::Background], )); state.prepare_theme_wallpaper(renderer, output); let progress = state.theme_progress(); let wallpaper = state.wallpaper.element(renderer, output); let has_wallpaper = wallpaper.is_some(); if let Some(mut wallpaper) = wallpaper { wallpaper.alpha = progress; elements.push(wallpaper.into()); } if let Some(mut previous) = state.previous_theme_wallpaper(renderer, output) { if !has_wallpaper { previous.alpha = 1. - progress; } elements.push(previous.into()); } backdrop::update(&elements, scale); elements } pub(crate) fn layer_surfaces(output: &Output) -> Vec { layer_map_for_output(output).layers().cloned().collect() } pub(super) fn layer_elements( state: &mut Ferese, renderer: &mut GlesRenderer, output: &Output, requested_layers: &[Layer], ) -> Vec { let scale = output.current_scale().fractional_scale(); let Some(mode) = output.current_mode() else { return Vec::new(); }; let output_crop = Rectangle::::from_size(mode.size); let layers = { let map = layer_map_for_output(output); requested_layers .iter() .flat_map(|requested| { map.layers_on(*requested) .rev() .filter_map(|layer| map.layer_geometry(layer).map(|geometry| (geometry, layer.clone()))) }) .collect::>() }; state.render.surfaces.retain(|surface, _| { surface.is_alive() || state .render .closing .iter() .any(|window| window.material.as_ref().is_some_and(|(key, ..)| key == surface)) }); 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; append_material_surface( state, renderer, output, popup.wl_surface(), bounds, content_origin, scale, output_crop, &mut elements, ); } append_material_surface( state, renderer, output, layer.wl_surface(), geometry, geometry.loc, scale, output_crop, &mut elements, ); } elements } pub(super) fn cursor_elements( state: &Ferese, renderer: &mut GlesRenderer, output_geometry: Rectangle, scale: f64, ) -> Vec { if state.input_capture.active() { return Vec::new(); } let Some(pointer) = state.seat.get_pointer() else { return Vec::new(); }; let output_crop = Rectangle::::from_size(output_geometry.size).to_physical_precise_round(scale); let origin = Point::::default(); let pointer_location = pointer.current_location() - output_geometry.loc.to_f64(); match &state.cursor_status { CursorImageStatus::Surface(surface) => { let hotspot = with_states(surface, |states| { states .data_map .get::() .map(|attributes| attributes.lock().unwrap().hotspot) .unwrap_or_default() }); let physical_location = (pointer_location - hotspot.to_f64()).to_physical_precise_round(scale); render_elements_from_surface_tree::>( renderer, surface, physical_location, scale, 1.0, RenderElementKind::Cursor, ) .into_iter() .filter_map(|element| { let element = RescaleRenderElement::from_element(element, origin, 1.0); let element = RelocateRenderElement::from_element(element, origin, Relocate::Relative); CropRenderElement::from_element(element, scale, output_crop).map(Into::into) }) .collect() } CursorImageStatus::Named(_) => { let Some(cursor) = state.named_cursor_frame() else { return Vec::new(); }; let physical_location = cursor.physical_location(pointer_location, scale); let Ok(element) = MemoryRenderBufferRenderElement::from_buffer( renderer, physical_location, &cursor.buffer, None, None, None, RenderElementKind::Cursor, ) else { return Vec::new(); }; let element = RescaleRenderElement::from_element(element, origin, 1.0); let element = RelocateRenderElement::from_element(element, origin, Relocate::Relative); CropRenderElement::from_element(element, scale, output_crop) .map(|element| vec![element.into()]) .unwrap_or_default() } CursorImageStatus::Hidden => Vec::new(), } }