text-input: re-sync on enter and flag secure fields as Password
LTK drives the on-screen keyboard through zwp_text_input_v3: focusing a text widget enables text-input so the compositor's input-method (squeekboard) brings the keyboard up. Two gaps are fixed here. Handle the `enter` event by re-emitting enable + content type + commit. The compositor sends `enter` when it (re)focuses the text input — notably when an input-method connects after the field has already enabled text-input (a startup race). LTK previously ignored `enter`, so that activation was lost and the keyboard never appeared; it now re-declares its state and the OSK comes up. Declare the content type from the field's `secure` flag: secure fields are flagged `ContentPurpose::Password` with `ContentHint::SensitiveData | HiddenText` (so the IME/OSK skips prediction, autocorrect and storing the value), everything else stays `Normal`/`None`. The content type is also refreshed when focus moves between two text fields (e.g. username → password) without re-creating the text-input object, and a new `AppData::text_input_secure` lets the `enter` re-emit path preserve the current field's type. Correct the docs: `TextEdit::secure()` and SECURITY.md claimed secure "skips text-input-v3 registration". It does not — the field still activates text-input so the OSK works on it; the protection is the Password / sensitive flagging, and the value still reaches a trusted compositor/IME.
This commit is contained in:
@@ -13,21 +13,34 @@ use crate::widget::WidgetHandlers;
|
||||
|
||||
impl<A: App> AppData<A>
|
||||
{
|
||||
pub( crate ) fn activate_text_input( &mut self, qh: &QueueHandle<Self> )
|
||||
pub( crate ) fn activate_text_input( &mut self, qh: &QueueHandle<Self>, secure: bool )
|
||||
{
|
||||
if let ( Some( manager ), None ) = ( &self.text_input_manager, &self.text_input )
|
||||
self.text_input_secure = secure;
|
||||
let ( hint, purpose ) = content_type( secure );
|
||||
match ( &self.text_input_manager, &self.text_input )
|
||||
{
|
||||
let seats: Vec<_> = self.seat_state.seats().collect();
|
||||
if let Some( seat ) = seats.into_iter().next()
|
||||
( Some( manager ), None ) =>
|
||||
{
|
||||
let ti = manager.get_text_input( &seat, qh, () );
|
||||
ti.enable();
|
||||
ti.set_content_type(
|
||||
zwp_text_input_v3::ContentHint::None,
|
||||
zwp_text_input_v3::ContentPurpose::Normal,
|
||||
);
|
||||
let seats: Vec<_> = self.seat_state.seats().collect();
|
||||
if let Some( seat ) = seats.into_iter().next()
|
||||
{
|
||||
let ti = manager.get_text_input( &seat, qh, () );
|
||||
ti.enable();
|
||||
ti.set_content_type( hint, purpose );
|
||||
ti.commit();
|
||||
self.text_input = Some( ti );
|
||||
} else {
|
||||
eprintln!( "ltk: activate_text_input: no seat available" );
|
||||
}
|
||||
}
|
||||
( None, _ ) =>
|
||||
eprintln!( "ltk: activate_text_input: no text_input_manager (compositor did not advertise zwp_text_input_manager_v3)" ),
|
||||
// Focus moved between text fields: refresh the content type (e.g.
|
||||
// flag a password field) without re-creating the object.
|
||||
( Some( _ ), Some( ti ) ) =>
|
||||
{
|
||||
ti.set_content_type( hint, purpose );
|
||||
ti.commit();
|
||||
self.text_input = Some( ti );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +55,17 @@ impl<A: App> AppData<A>
|
||||
}
|
||||
}
|
||||
|
||||
pub( crate ) fn reenable_text_input( &self )
|
||||
{
|
||||
if let Some( ti ) = &self.text_input
|
||||
{
|
||||
let ( hint, purpose ) = content_type( self.text_input_secure );
|
||||
ti.enable();
|
||||
ti.set_content_type( hint, purpose );
|
||||
ti.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot a focused-widget's geometry needed by the pointer
|
||||
/// hit-testers for text editing. Returns `None` when the widget
|
||||
/// isn't a TextEdit or its rect is missing — the helper above
|
||||
@@ -69,3 +93,19 @@ impl<A: App> AppData<A>
|
||||
Some( ( widget.rect, value, multiline, secure, align, font_size ) )
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a field's `secure` flag to the text-input-v3 content type. Secure
|
||||
/// fields are flagged `Password` with `SensitiveData | HiddenText` so the
|
||||
/// IME/OSK skips prediction, autocorrect and storing the value.
|
||||
fn content_type( secure: bool ) -> ( zwp_text_input_v3::ContentHint, zwp_text_input_v3::ContentPurpose )
|
||||
{
|
||||
if secure
|
||||
{
|
||||
(
|
||||
zwp_text_input_v3::ContentHint::SensitiveData | zwp_text_input_v3::ContentHint::HiddenText,
|
||||
zwp_text_input_v3::ContentPurpose::Password,
|
||||
)
|
||||
} else {
|
||||
( zwp_text_input_v3::ContentHint::None, zwp_text_input_v3::ContentPurpose::Normal )
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user