Toplevel events name the window, apps hear the pointer hover a surface and where their blurred cards were laid out, and rows can align to the bottom
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s

A shell could not tell the windows of an app apart, follow the mouse over one of its overlays, or tell its compositor where the translucent cards of a surface ended up. crustace's floating dock needs all three, to activate and close single windows, to magnify the icons under the pointer and to have forge blur behind its pill and its Quick Settings cards.
`ToplevelEvent::Opened` carries the handle's `identifier`, the compositor's token for that one window, and its `title`, both from `ForeignToplevelInfo`; `toplevel_opened` builds the event for the new and the updated handle. Matches on `Opened { id, app_id }` have to add the fields or `..`.
`App::on_pointer_hover( SurfaceTarget, Option<( x, y )> )` is called on every mouse motion over a surface, in its physical pixels, and with `None` from the pointer's `leave`, which ltk did not handle before. `SurfaceFocus::target` gives the `SurfaceTarget` of a focus.
`Container::blur_behind()` marks a container whose rect and largest corner radius, capped at half its shorter side, the layout pass records in surface coordinates through the `DrawCtx::origin` offset of the sub-canvases, clipped by the viewports it sits in. `commit_draw_ctx` keeps them in `FrameState`, and after a frame where they changed `draw_frame` hands them to `App::on_blur_regions( SurfaceTarget, &[ ( Rect, f32 ) ] )`, for the main surface and each overlay.
`Row::align_bottom()` pins the children to the bottom edge, so taller siblings grow upward. `docs/architecture.md` lists `on_pointer_hover` among the input hooks.
Not done. `on_blur_regions`, `blur_behind` and `align_bottom` are not in the documentation, and there are no tests for them. The regions are reported after the frame is committed, so a compositor that receives them over IPC is one frame behind a surface whose cards move.
This commit is contained in:
2026-10-15 14:49:25 +02:00
parent 35ab8ac2b0
commit 3edd822918
13 changed files with 105 additions and 15 deletions
+9 -1
View File
@@ -6,7 +6,7 @@ use smithay_client_toolkit::compositor::CompositorState;
use smithay_client_toolkit::reexports::client::protocol::{ wl_shm, wl_surface::WlSurface };
use smithay_client_toolkit::shm::Shm;
use crate::app::App;
use crate::app::{ App, SurfaceTarget };
use crate::egl_context::EglContext;
use crate::types::{ Color, Rect };
use crate::widget::Element;
@@ -77,6 +77,10 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> bool
first_commit_now = true;
}
data.app.on_main_frame_committed();
if std::mem::take( &mut data.main.frame.blur_changed )
{
data.app.on_blur_regions( SurfaceTarget::Main, &data.main.frame.blur_regions );
}
}
for ( _, ss ) in data.secondaries.iter_mut()
@@ -136,6 +140,10 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> bool
ss.needs_redraw = false;
ss.last_draw = std::time::Instant::now();
data.app.on_overlay_frame_committed( spec.id );
if std::mem::take( &mut ss.frame.blur_changed )
{
data.app.on_blur_regions( SurfaceTarget::Overlay( spec.id ), &ss.frame.blur_regions );
}
}
}
+12 -10
View File
@@ -710,10 +710,10 @@ impl<A: App> ProvidesRegistryState for AppData<A>
/// `identifier` (always present, unique per handle). Without the cascade,
/// any client that never set `app_id` (e.g. winit-windowed compositors
/// before they bind their xdg surface) was silently invisible to the dock.
fn toplevel_display_id(
fn toplevel_opened(
list: &ForeignToplevelList,
handle: &ExtForeignToplevelHandleV1,
) -> String
) -> ToplevelEvent
{
// Only ever surface the client's `app_id`. The protocol-level
// `identifier` is a server-internal random opaque token, not an
@@ -723,8 +723,12 @@ fn toplevel_display_id(
// the random identifier as their "app id" until the client's
// real `set_app_id` lands. `title` is also unsuitable — it
// changes every time the window's title text updates.
let Some( info ) = list.info( handle ) else { return String::new(); };
info.app_id
let id = handle.id().protocol_id();
match list.info( handle )
{
Some( info ) => ToplevelEvent::Opened { id, app_id: info.app_id, identifier: info.identifier, title: info.title },
None => ToplevelEvent::Opened { id, app_id: String::new(), identifier: String::new(), title: String::new() },
}
}
impl<A: App> ForeignToplevelListHandler for AppData<A>
@@ -741,9 +745,8 @@ impl<A: App> ForeignToplevelListHandler for AppData<A>
handle: ExtForeignToplevelHandleV1,
)
{
let id = handle.id().protocol_id();
let app_id = toplevel_display_id( &self.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( ToplevelEvent::Opened { id, app_id } )
let event = toplevel_opened( &self.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( event )
{
self.pending_msgs.push( msg );
}
@@ -763,9 +766,8 @@ impl<A: App> ForeignToplevelListHandler for AppData<A>
// can treat repeated opens as idempotent and pay nothing here,
// but the app_id-can-change-mid-life case still produces a
// correct sequence (old refcount drops, new one bumps).
let id = handle.id().protocol_id();
let app_id = toplevel_display_id( &self.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( ToplevelEvent::Opened { id, app_id } )
let event = toplevel_opened( &self.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( event )
{
self.pending_msgs.push( msg );
}
+1
View File
@@ -330,6 +330,7 @@ fn empty_draw_ctx<Msg: Clone>() -> DrawCtx<Msg>
accessible_extras: Vec::new(),
live_depth: 0,
origin: ( 0.0, 0.0 ),
blur_regions: Vec::new(),
}
}
+18 -1
View File
@@ -22,7 +22,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use super::app_data::AppData;
use crate::app::OverlayId;
use crate::app::{ OverlayId, SurfaceTarget };
use crate::egl_context::{ EglContext, EglSurface };
use crate::input::GestureState;
use crate::render::Canvas;
@@ -42,6 +42,18 @@ pub( crate ) enum SurfaceFocus
Overlay( OverlayId ),
}
impl SurfaceFocus
{
pub( crate ) fn target( self ) -> SurfaceTarget
{
match self
{
SurfaceFocus::Main => SurfaceTarget::Main,
SurfaceFocus::Overlay( id ) => SurfaceTarget::Overlay( id ),
}
}
}
/// Configuration for a layer-shell surface, used both for the main surface
/// (when the app uses [`crate::app::ShellMode::Layer`]) and for each overlay
/// returned by [`crate::app::App::overlays`].
@@ -187,6 +199,9 @@ pub( crate ) struct FrameState<Msg: Clone>
pub prev_focused: Option<usize>,
pub prev_hovered: Option<usize>,
pub prev_pressed: Option<usize>,
/// `blur_behind` containers of the last frame, and whether they changed since reported.
pub blur_regions: Vec<( Rect, f32 )>,
pub blur_changed: bool,
}
impl<Msg: Clone> FrameState<Msg>
@@ -209,6 +224,8 @@ impl<Msg: Clone> FrameState<Msg>
prev_focused: None,
prev_hovered: None,
prev_pressed: None,
blur_regions: Vec::new(),
blur_changed: false,
}
}
}