From 2cee9a1c57d449015a576eb7e1d7df4ed4a408f6 Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Thu, 10 Sep 2026 21:12:45 +0200 Subject: [PATCH] event_loop: drop the text-input focus when the focused field vanishes from a rebuilt view 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. --- src/event_loop/focus.rs | 29 +++++++++++++++++++++++++++++ src/event_loop/frame.rs | 15 +++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/event_loop/focus.rs b/src/event_loop/focus.rs index 0dda36f..02f2f06 100644 --- a/src/event_loop/focus.rs +++ b/src/event_loop/focus.rs @@ -80,6 +80,23 @@ impl AppData .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, qh: &QueueHandle ) { let was_text_input; @@ -168,3 +185,15 @@ impl AppData } } } + +/// Whether the surface's focused widget is a text input in its current frame. +pub( crate ) fn surface_focused_text_input( ss: &super::surface::SurfaceState ) -> 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() ) +} diff --git a/src/event_loop/frame.rs b/src/event_loop/frame.rs index 653be6c..b2ac7cd 100644 --- a/src/event_loop/frame.rs +++ b/src/event_loop/frame.rs @@ -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( data: &mut AppData ) -> bool let qh = &data.qh; let mut first_commit_now = false; + let mut text_focus_rebuilt: Vec = 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::( &mut data.main, @@ -104,6 +110,10 @@ pub( crate ) fn draw_frame( data: &mut AppData ) -> 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::( @@ -123,6 +133,11 @@ pub( crate ) fn draw_frame( data: &mut AppData ) -> bool } } + for focus in text_focus_rebuilt + { + data.reconcile_text_focus( focus ); + } + first_commit_now }