diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 896ca859..8cef290e 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -7,7 +7,7 @@ use crate::platform_specific::SurfaceIdWrapper; use crate::{ Control, futures::futures::channel::mpsc, - handlers::overlap::OverlapNotifyV1, + handlers::{overlap::OverlapNotifyV1, text_input::TextInputManager}, platform_specific::wayland::{ handlers::{ wp_fractional_scaling::FractionalScalingManager, @@ -52,8 +52,8 @@ use std::{ use log::error; use wayland_backend::client::Backend; use wayland_client::globals::GlobalError; -use wayland_protocols::wp::keyboard_shortcuts_inhibit::zv1::client::zwp_keyboard_shortcuts_inhibit_manager_v1; -use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle}; +use wayland_protocols::wp::{keyboard_shortcuts_inhibit::zv1::client::zwp_keyboard_shortcuts_inhibit_manager_v1, text_input::zv3::client::zwp_text_input_v3::{ContentHint, ContentPurpose}}; +use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle, window::ImePurpose}; use self::state::SctkState; @@ -197,6 +197,33 @@ impl SctkEventLoop { crate::platform_specific::Action::Dropped(id) => { _ = state.destroyed.remove(&id.inner()); } + crate::platform_specific::Action::SetImeAllowed(allowed) => { + if let Some(text_input) = state.text_input.as_ref() { + if allowed { + text_input.enable(); + } else { + text_input.disable(); + } + text_input.commit(); + } + } + crate::platform_specific::Action::SetImeCursorArea(x, y, width, height) => { + if let Some(text_input) = state.text_input.as_ref() { + text_input.set_cursor_rectangle(x, y, width, height); + text_input.commit(); + } + } + crate::platform_specific::Action::SetImePurpose(purpose) => { + if let Some(text_input) = state.text_input.as_ref() { + let (hint, purpose) = match purpose { + ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password), + ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal), + _ => (ContentHint::None, ContentPurpose::Normal), + }; + text_input.set_content_type(hint, purpose); + text_input.commit(); + } + } crate::platform_specific::Action::SubsurfaceResize(id, size) => { // reposition the surface if let Some(pos) = state @@ -332,6 +359,7 @@ impl SctkEventLoop { 1..=1, (), ).ok(), + text_input_manager: TextInputManager::try_new(®istry_state, &qh), registry_state, queue_handle: qh, @@ -366,6 +394,9 @@ impl SctkEventLoop { overlap_notifications: HashMap::new(), subsurface_state: None, pending_corner_radius: HashMap::new(), + text_input: None, + preedit: None, + pending_commit: None, }, _features: Default::default(), }; diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 3c37a4a2..aca83635 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -3,6 +3,7 @@ use crate::{ handlers::{ activation::IcedRequestData, overlap::{OverlapNotificationV1, OverlapNotifyV1}, + text_input::{Preedit, TextInputManager}, }, platform_specific::{ Event, @@ -113,6 +114,7 @@ use wayland_protocols::{ zwp_keyboard_shortcuts_inhibit_manager_v1, zwp_keyboard_shortcuts_inhibitor_v1, }, + text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3, viewporter::client::wp_viewport::WpViewport, }, xdg::shell::client::{xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel}, @@ -492,7 +494,12 @@ pub struct SctkState { pub(crate) inhibitor_manager: Option, pub(crate) corner_radius_manager: Option, - pub(crate) pending_corner_radius: HashMap + pub(crate) pending_corner_radius: HashMap, + + pub(crate) text_input_manager: Option, + pub(crate) text_input: Option>, + pub(crate) preedit: Option, + pub(crate) pending_commit: Option, } /// An error that occurred while running an application. diff --git a/winit/src/platform_specific/wayland/handlers/mod.rs b/winit/src/platform_specific/wayland/handlers/mod.rs index 856c5c32..dfc73b5e 100644 --- a/winit/src/platform_specific/wayland/handlers/mod.rs +++ b/winit/src/platform_specific/wayland/handlers/mod.rs @@ -7,6 +7,7 @@ pub mod seat; pub mod session_lock; pub mod shell; pub mod subcompositor; +pub mod text_input; pub mod toplevel; pub mod wp_fractional_scaling; pub mod wp_viewporter; diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs index 71ae42de..2901ce8e 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/seat.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -8,6 +8,7 @@ use cctk::sctk::{ seat::{pointer::ThemeSpec, SeatHandler}, }; use iced_runtime::keyboard::Modifiers; +use std::sync::Arc; impl SeatHandler for SctkState { fn seat_state(&mut self) -> &mut cctk::sctk::seat::SeatState { @@ -141,6 +142,16 @@ impl SeatHandler for SctkState { } _ => unimplemented!(), } + + if let Some(text_input_manager) = self + .text_input + .is_none() + .then_some(self.text_input_manager.as_ref()) + .flatten() + { + self.text_input = + Some(Arc::new((text_input_manager).get_text_input(&seat, &qh))); + } } fn remove_capability( @@ -193,6 +204,10 @@ impl SeatHandler for SctkState { } _ => unimplemented!(), } + + if let Some(text_input) = self.text_input.take() { + text_input.destroy(); + } } fn remove_seat( diff --git a/winit/src/platform_specific/wayland/handlers/text_input.rs b/winit/src/platform_specific/wayland/handlers/text_input.rs new file mode 100644 index 00000000..ed1ee8c9 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/text_input.rs @@ -0,0 +1,130 @@ +use cctk::sctk::globals::GlobalData; +use cctk::sctk::reexports::client::{Connection, Proxy, QueueHandle}; + +use cctk::sctk::reexports::client::delegate_dispatch; +use cctk::sctk::reexports::client::Dispatch; +use cctk::sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use cctk::sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent; +use cctk::sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; +use cctk::sctk::registry::RegistryState; +use wayland_client::protocol::wl_seat::WlSeat; +use winit::event::{Ime, WindowEvent}; +use winit::window::WindowId; + +use crate::event_loop::state::SctkState; +use crate::sctk_event::SctkEvent; + +pub struct Preedit { + text: String, + cursor_range: Option<(usize, usize)>, +} + +pub struct TextInputManager { + manager: ZwpTextInputManagerV3, +} + +impl TextInputManager { + pub fn try_new( + registry: &RegistryState, + qh: &QueueHandle, + ) -> Option + where + D: Dispatch + 'static, + { + let manager = registry + .bind_one::(qh, 1..=1, GlobalData) + .ok()?; + Some(Self { manager }) + } + + pub fn get_text_input( + &self, + seat: &WlSeat, + qh: &QueueHandle, + ) -> ZwpTextInputV3 { + self.manager.get_text_input(&seat, &qh, ()) + } +} + +impl Dispatch + for TextInputManager +{ + fn event( + _state: &mut SctkState, + _proxy: &ZwpTextInputManagerV3, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for TextInputManager { + fn event( + state: &mut SctkState, + _text_input: &ZwpTextInputV3, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + let kbd_focus = + match state.seats.iter_mut().find_map(|s| s.kbd_focus.clone()) { + Some(surface) => surface, + None => return, + }; + match event { + TextInputEvent::PreeditString { + text, + cursor_begin, + cursor_end, + } => { + let text = text.unwrap_or_default(); + let cursor_begin = usize::try_from(cursor_begin) + .ok() + .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); + let cursor_end = usize::try_from(cursor_end) + .ok() + .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); + let cursor_range = + cursor_begin.map(|b| (b, cursor_end.unwrap_or(b))); + state.preedit = Some(Preedit { text, cursor_range }); + } + TextInputEvent::CommitString { text } => { + state.preedit = None; + state.pending_commit = text; + } + TextInputEvent::Done { .. } => { + let id = WindowId::from_raw(kbd_focus.id().as_ptr() as usize); + state.sctk_events.push(SctkEvent::Winit( + id, + WindowEvent::Ime(Ime::Preedit(String::new(), None)), + )); + + // Commit string + if let Some(text) = state.pending_commit.take() { + state.sctk_events.push(SctkEvent::Winit( + id, + WindowEvent::Ime(Ime::Commit(text)), + )); + } + + // Update preedit string + if let Some(preedit) = state.preedit.take() { + state.sctk_events.push(SctkEvent::Winit( + id, + WindowEvent::Ime(Ime::Preedit( + preedit.text, + preedit.cursor_range, + )), + )); + } + } + _ => {} + } + } +} + +delegate_dispatch!(SctkState: [ZwpTextInputManagerV3: GlobalData] => TextInputManager); +delegate_dispatch!(SctkState: [ZwpTextInputV3: ()] => TextInputManager); diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 7ae0db68..9b3a7ec0 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -27,6 +27,7 @@ use wayland_backend::client::ObjectId; use wayland_client::{Connection, Proxy}; use winit::dpi::Size; use winit::event_loop::OwnedDisplayHandle; +use winit::window::ImePurpose; pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), @@ -37,6 +38,9 @@ pub(crate) enum Action { RemoveWindow(window::Id), Dropped(SurfaceIdWrapper), SubsurfaceResize(window::Id, Size), + SetImeAllowed(bool), + SetImeCursorArea(i32, i32, i32, i32), + SetImePurpose(ImePurpose), } impl std::fmt::Debug for Action { @@ -64,6 +68,19 @@ impl std::fmt::Debug for Action { Self::ResizeWindow(arg0) => { f.debug_tuple("ResizeWindow").field(arg0).finish() } + Self::SetImeAllowed(allowed) => { + f.debug_tuple("SetImeAllowed").field(allowed).finish() + } + Self::SetImeCursorArea(x, y, width, height) => f + .debug_tuple("SetImeCursorArea") + .field(x) + .field(y) + .field(width) + .field(height) + .finish(), + Self::SetImePurpose(purpose) => { + f.debug_tuple("SetImePurpose").field(purpose).finish() + } } } } diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index a63e00b7..296d8b67 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -266,15 +266,24 @@ impl winit::window::Window for SctkWinitWindow { position: winit::dpi::Position, size: winit::dpi::Size, ) { - todo!() + let guard = self.common.lock().unwrap(); + let scale_factor = guard.fractional_scale.unwrap_or(1.); + let position = position.to_logical(scale_factor); + let size = size.to_logical(scale_factor); + _ = self.tx.send(Action::SetImeCursorArea( + position.x, + position.y, + size.width, + size.height, + )); } fn set_ime_allowed(&self, allowed: bool) { - todo!() + _ = self.tx.send(Action::SetImeAllowed(allowed)); } fn set_ime_purpose(&self, purpose: winit::window::ImePurpose) { - todo!() + _ = self.tx.send(Action::SetImePurpose(purpose)); } fn set_blur(&self, blur: bool) {