event_loop: drop the text-input focus when the focused field vanishes from a rebuilt view
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

A view rebuild that no longer contains the focused text field left `focused_idx` pointing at a stale index and the `zwp_text_input_v3` object enabled. The compositor kept seeing an active text-input, so the on-screen keyboard stayed up over a screen with nothing to type into. The lockscreen's inactivity return from the password form to the idle screen is the visible case: the form disappears, the keyboard does not.
`focus.rs` gains `surface_focused_text_input`, which resolves the focused widget by id when it has one and by index otherwise and reports whether it is a text input, and `reconcile_text_focus`, which clears the focus, deactivates the text-input and calls `on_text_input_focused( false )` when it no longer resolves to one. `draw_frame` notes, before drawing the main surface and each overlay, whether its focus was a text input, and reconciles those surfaces once every draw is done and no view borrow is live. Focus that lands on the field again through `take_focus_request` re-enables the text-input as before.
This commit is contained in:
2026-09-10 21:12:45 +02:00
parent 492553829e
commit 2cee9a1c57
2 changed files with 44 additions and 0 deletions
+29
View File
@@ -80,6 +80,23 @@ impl<A: App> AppData<A>
.and_then( |s| s.on_dismiss )
}
/// After a rebuild: a focused text input that vanished from the frame
/// would otherwise keep the text-input protocol (and the OSK) active.
pub( crate ) fn reconcile_text_focus( &mut self, focus: SurfaceFocus )
{
if surface_focused_text_input( self.surface( focus ) ) { return; }
let ss = self.surface_mut( focus );
if let Some( idx ) = ss.focused_idx.take()
{
ss.pending_text_values.remove( &idx );
}
ss.focused_id = None;
ss.request_redraw();
self.deactivate_text_input();
self.app.on_text_input_focused( false );
self.dirty_caches();
}
pub( crate ) fn set_focus( &mut self, focus: SurfaceFocus, idx: Option<usize>, qh: &QueueHandle<Self> )
{
let was_text_input;
@@ -168,3 +185,15 @@ impl<A: App> AppData<A>
}
}
}
/// Whether the surface's focused widget is a text input in its current frame.
pub( crate ) fn surface_focused_text_input<Msg: Clone>( ss: &super::surface::SurfaceState<Msg> ) -> bool
{
let by_id = ss.focused_id.and_then( |id|
{
ss.frame.widget_rects.iter().find( |w| w.id == Some( id ) ).map( |w| &w.handlers )
} );
by_id
.or_else( || ss.focused_idx.and_then( |i| find_handlers( &ss.frame.widget_rects, i ) ) )
.is_some_and( |h| h.is_text_input() )
}
+15
View File
@@ -12,6 +12,7 @@ use crate::types::{ Color, Rect };
use crate::widget::Element;
use super::AppData;
use super::focus::surface_focused_text_input;
use super::surface::{ SurfaceFocus, SurfaceState };
use crate::draw::gles::{ draw_surface_full_gpu, draw_surface_partial_gpu };
use crate::draw::software::{ draw_surface_full, draw_surface_partial };
@@ -47,8 +48,13 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> bool
let qh = &data.qh;
let mut first_commit_now = false;
let mut text_focus_rebuilt: Vec<SurfaceFocus> = Vec::new();
if data.main.configured && data.main.needs_redraw && !data.main.frame_pending
{
if surface_focused_text_input( &data.main )
{
text_focus_rebuilt.push( SurfaceFocus::Main );
}
let req_frame = | wl: &WlSurface | { let _ = wl.frame( qh, SurfaceFocus::Main ); };
draw_surface::<A::Message>(
&mut data.main,
@@ -104,6 +110,10 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> bool
{
if !ss.configured || !ss.needs_redraw || ss.frame_pending { continue; }
let focus = SurfaceFocus::Overlay( spec.id );
if surface_focused_text_input( ss )
{
text_focus_rebuilt.push( focus );
}
let req_frame = | wl: &WlSurface | { let _ = wl.frame( qh, focus ); };
let bg = Color::rgba( 0.0, 0.0, 0.0, 0.0 );
draw_surface::<A::Message>(
@@ -123,6 +133,11 @@ pub( crate ) fn draw_frame<A: App>( data: &mut AppData<A> ) -> bool
}
}
for focus in text_focus_rebuilt
{
data.reconcile_text_focus( focus );
}
first_commit_now
}