// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use smithay_client_toolkit::reexports::client::QueueHandle; use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3; use crate::app::App; use crate::event_loop::app_data::AppData; use crate::event_loop::surface::SurfaceFocus; use crate::tree::find_widget; use crate::types::{ Length, Rect }; use crate::widget::WidgetHandlers; impl AppData { pub( crate ) fn activate_text_input( &mut self, qh: &QueueHandle, secure: bool ) { self.text_input_secure = secure; let ( hint, purpose ) = content_type( secure ); match ( &self.text_input_manager, &self.text_input ) { ( Some( manager ), None ) => { 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(); } } } pub( crate ) fn deactivate_text_input( &mut self ) { if let Some( ti ) = self.text_input.take() { ti.disable(); ti.commit(); ti.destroy(); } } 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 /// short-circuits in that case. pub( crate ) fn text_input_geometry( &self, focus: SurfaceFocus, idx: usize, ) -> Option<( Rect, String, bool, bool, crate::widget::text::TextAlign, Option )> { let ss = self.surface( focus ); let widget = find_widget( &ss.frame.widget_rects, idx )?; let ( value_handler, multiline, secure, align, font_size ) = match &widget.handlers { WidgetHandlers::TextEdit { value, multiline, secure, align, font_size, .. } => ( value.clone(), *multiline, *secure, *align, *font_size ), _ => return None, }; // Prefer the *pending* value (typed-but-not-yet-applied) when // it exists — that's what the user sees right now and what // the cursor measurements should be relative to. let value = ss.pending_text_values.get( &idx ) .cloned() .unwrap_or( value_handler ); 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 ) } }