use std::collections::HashSet;
use smithay_client_toolkit::
{
compositor::Surface,
shell::
{
WaylandSurface,
xdg::
{
XdgPositioner, XdgSurface,
popup::Popup,
window::WindowDecorations,
},
},
};
use wayland_protocols::xdg::shell::client::xdg_positioner::
{
Anchor as PositionerAnchor,
ConstraintAdjustment,
Gravity,
};
use crate::app::App;
use crate::event_loop::{ AppData, SurfaceState };
use crate::event_loop::tooltip::tooltip_id;
use crate::types::Rect;
use super::{ LayerConfig, SurfaceKind, WaylandSurfaceState };
const TOOLTIP_GAP: i32 = 6;
pub( super ) fn reconcile_overlays<A: App>( data: &mut AppData<A> )
{
let mut specs = data.app.overlays();
if let Some( ts ) = data.tooltip_overlay() { specs.push( ts ); }
let tooltip_popup = data.tooltip_popup();
let next_ids: Vec<_> = specs.iter().map( |s| s.id ).collect();
let wanted: HashSet<crate::app::OverlayId> = next_ids.iter().copied().collect();
let removed: Vec<_> = data.overlays.keys()
.filter( |id| !wanted.contains( id ) )
.copied()
.collect();
for id in removed
{
let Some( mut ss ) = data.remove_overlay( id ) else { continue };
if let Some( canvas ) = ss.canvas.take()
{
if matches!( canvas, crate::render::Canvas::Gles( _ ) )
{
data.platform.canvas_pool.push( canvas );
}
}
}
let sizes: Vec<( u32, u32 )> = specs.iter().map( |s| data.overlay_size( &s.size ) ).collect();
let layer_shell_opt = data.platform.layer_shell.as_ref();
let xdg_shell_opt = data.platform.xdg_shell.as_ref();
let output_opt = data.platform.output_state.outputs().next();
let cs = &data.platform.compositor_state;
let qh = &data.platform.qh;
let grab_seat = data.platform.seat_state.seats().next();
let grab_serial = data.platform.last_input_serial;
let parent_xdg = match data.main.platform.surface
{
SurfaceKind::Window( ref w ) => Some( w.xdg_surface().clone() ),
_ => None,
};
let parent_scale = data.main.scale_factor;
let to_logical_size = move | ( w, h ): ( u32, u32 ) | -> ( u32, u32 )
{
(
( w as f32 / parent_scale ).round() as u32,
( h as f32 / parent_scale ).round() as u32,
)
};
let to_logical_zone = move | z: i32 | -> i32
{
if z > 0 { ( z as f32 / parent_scale ).round() as i32 } else { z }
};
let main_widget_rects = &data.main.frame.widget_rects;
let app = &data.app;
let overlays_m = &mut data.overlays;
let canvas_pool = &mut data.platform.canvas_pool;
let has_egl = data.platform.egl_context.is_some();
for ( spec, &resolved_size ) in specs.iter().zip( &sizes )
{
if let Some( ss ) = overlays_m.get_mut( &spec.id )
{
let resolved_zone = to_logical_zone( spec.exclusive_zone );
if resolved_size != ss.platform.last_requested_size || resolved_zone != ss.platform.last_requested_zone
{
if let SurfaceKind::Layer( ref layer_surface ) = ss.platform.surface
{
if resolved_size != ss.platform.last_requested_size
{
let ( lw, lh ) = to_logical_size( resolved_size );
layer_surface.set_size( lw, lh );
ss.platform.last_requested_size = resolved_size;
}
if resolved_zone != ss.platform.last_requested_zone
{
layer_surface.set_exclusive_zone( resolved_zone );
ss.platform.last_requested_zone = resolved_zone;
}
layer_surface.commit();
}
}
if let SurfaceKind::Popup( ref popup ) = ss.platform.surface
{
if let ( Some( anchor_id ), Some( xdg_shell ) ) = ( spec.anchor_widget_id, xdg_shell_opt )
{
if let Some( anchor_rect ) = main_widget_rects.iter()
.find( |w| w.id == Some( anchor_id ) )
.map( |w| w.rect )
{
let to_logical = | r: Rect |
{
(
( r.x / parent_scale ).round() as i32,
( r.y / parent_scale ).round() as i32,
( r.width / parent_scale ).round().max( 1.0 ) as i32,
( r.height / parent_scale ).round().max( 1.0 ) as i32,
)
};
let new_q = to_logical( anchor_rect );
let moved = ss.platform.last_popup_anchor.map( |r| to_logical( r ) != new_q ).unwrap_or( true );
if moved
{
if let Ok( positioner ) = XdgPositioner::new( xdg_shell )
{
let ( ax, ay, aw, ah ) = new_q;
let ( spec_w, spec_h ) = resolved_size;
let popup_w = if spec_w == 0 { aw } else { spec_w.max( 1 ) as i32 };
let popup_h = spec_h.max( 1 ) as i32;
positioner.set_size( popup_w, popup_h );
positioner.set_anchor_rect( ax, ay, aw, ah );
positioner.set_anchor( PositionerAnchor::Bottom );
positioner.set_gravity( Gravity::Bottom );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX,
);
ss.platform.popup_reposition_token = ss.platform.popup_reposition_token.wrapping_add( 1 );
popup.reposition( &positioner, ss.platform.popup_reposition_token );
ss.platform.last_popup_anchor = Some( anchor_rect );
}
}
}
}
}
continue;
}
if spec.id == tooltip_id()
{
if let ( Some( ( anchor, ( w, h ) ) ), Some( xdg_shell ), Some( parent ) ) = ( tooltip_popup, xdg_shell_opt, parent_xdg.as_ref() )
{
let Ok( positioner ) = XdgPositioner::new( xdg_shell ) else { continue };
positioner.set_size( w.max( 1 ), h.max( 1 ) );
positioner.set_anchor_rect(
( anchor.x / parent_scale ).round() as i32,
( anchor.y / parent_scale ).round() as i32,
( anchor.width / parent_scale ).round().max( 1.0 ) as i32,
( anchor.height / parent_scale ).round().max( 1.0 ) as i32,
);
positioner.set_anchor( PositionerAnchor::Top );
positioner.set_gravity( Gravity::Top );
positioner.set_offset( 0, -TOOLTIP_GAP );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX | ConstraintAdjustment::SlideY,
);
let Ok( surface ) = Surface::new( cs, qh ) else { continue };
let Ok( popup ) = Popup::from_surface( Some( parent ), &positioner, qh, surface, xdg_shell ) else { continue };
popup.wl_surface().commit();
let mut ss = SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Popup( popup ) ), 0.0, String::new() );
ss.platform.last_requested_size = resolved_size;
overlays_m.insert( spec.id, ss );
continue;
}
}
if let Some( anchor_id ) = spec.anchor_widget_id
{
let Some( xdg_shell ) = xdg_shell_opt else
{
eprintln!( "ltk: ignoring popup overlay {:?} — main surface is not an xdg-shell window", spec.id );
continue;
};
let Some( ref parent ) = parent_xdg else
{
eprintln!( "ltk: ignoring popup overlay {:?} — no xdg parent surface", spec.id );
continue;
};
let Some( anchor_rect ) = main_widget_rects.iter()
.find( |w| w.id == Some( anchor_id ) )
.map( |w| w.rect )
else
{
eprintln!( "ltk: popup overlay {:?} could not find anchor widget id {:?}", spec.id, anchor_id );
continue;
};
let positioner = match XdgPositioner::new( xdg_shell )
{
Ok( p ) => p,
Err( e ) =>
{
eprintln!( "ltk: XdgPositioner::new failed for popup {:?}: {e}", spec.id );
continue;
}
};
let ax = ( anchor_rect.x / parent_scale ).round() as i32;
let ay = ( anchor_rect.y / parent_scale ).round() as i32;
let aw = ( anchor_rect.width / parent_scale ).round().max( 1.0 ) as i32;
let ah = ( anchor_rect.height / parent_scale ).round().max( 1.0 ) as i32;
let ( spec_w, spec_h ) = resolved_size;
let popup_w = if spec_w == 0 { aw } else { spec_w.max( 1 ) as i32 };
let popup_h = spec_h.max( 1 ) as i32;
positioner.set_size( popup_w, popup_h );
positioner.set_anchor_rect( ax, ay, aw, ah );
positioner.set_anchor( PositionerAnchor::Bottom );
positioner.set_gravity( Gravity::Bottom );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX,
);
let surface = match Surface::new( cs, qh )
{
Ok( s ) => s,
Err( e ) =>
{
eprintln!( "ltk: Surface::new failed for popup overlay {:?}: {e}", spec.id );
continue;
}
};
let popup = match Popup::from_surface( Some( parent ), &positioner, qh, surface, xdg_shell )
{
Ok( p ) => p,
Err( e ) =>
{
eprintln!( "ltk: Popup::from_surface failed for overlay {:?}: {e}", spec.id );
continue;
}
};
if let Some( ref seat ) = grab_seat
{
popup.xdg_popup().grab( seat, grab_serial );
}
popup.wl_surface().commit();
let mut ss = SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Popup( popup ) ), 0.0, String::new() );
ss.platform.last_requested_size = resolved_size;
ss.platform.last_popup_anchor = Some( anchor_rect );
overlays_m.insert( spec.id, ss );
continue;
}
if spec.layer == crate::app::Layer::Window
{
let Some( xdg_shell ) = xdg_shell_opt else
{
eprintln!( "ltk: ignoring window overlay {:?} — xdg_wm_base not available", spec.id );
continue;
};
let ( w, h ) = to_logical_size( resolved_size );
let ( w, h ) = ( if w == 0 { 480 } else { w }, if h == 0 { 320 } else { h } );
let surface = cs.create_surface( qh );
let window = xdg_shell.create_window( surface, WindowDecorations::RequestServer, qh );
let app_id = app.app_id().to_string();
window.set_title( app.overlay_title( spec.id ).unwrap_or_else( || app_id.clone() ) );
window.set_app_id( app_id );
window.set_min_size( Some( ( w, h ) ) );
window.set_max_size( Some( ( w, h ) ) );
window.commit();
let mut ss = SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Window( window ) ), 0.0, String::new() );
ss.scale_factor = parent_scale;
ss.platform.last_requested_size = resolved_size;
overlays_m.insert( spec.id, ss );
continue;
}
let Some( layer_shell ) = layer_shell_opt else
{
eprintln!( "ltk: ignoring layer-shell overlay {:?} — wlr-layer-shell not available", spec.id );
continue;
};
let cfg = LayerConfig
{
layer: spec.layer.to_wlr_layer(),
exclusive_zone: to_logical_zone( spec.exclusive_zone ),
anchor: spec.anchor,
size: to_logical_size( resolved_size ),
keyboard_exclusive: spec.keyboard_exclusive,
namespace: app.overlay_namespace( spec.id ).unwrap_or( "ltk-overlay" ),
};
let mut surface = SurfaceKind::Pending( cfg );
if let Some( ref output ) = output_opt
{
surface.materialize( cs, layer_shell, qh, output );
}
let mut ss = SurfaceState::new( WaylandSurfaceState::from( surface ), 0.0, String::new() );
ss.scale_factor = parent_scale;
ss.platform.last_requested_size = resolved_size;
ss.platform.last_requested_zone = to_logical_zone( spec.exclusive_zone );
ss.layer_anchor = Some( spec.anchor );
if has_egl { ss.canvas = canvas_pool.pop(); }
overlays_m.insert( spec.id, ss );
}
}