diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index daf342296f..0a98fb4b0b 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -394,6 +394,34 @@ impl CompositorHandler for State { return; } + for popup in self + .niri + .input_method_v1_popups + .iter() + .filter(|p| p.alive()) + { + let mut popup_root = popup.wl_surface().clone(); + while let Some(parent) = get_parent(&popup_root) { + popup_root = parent; + } + + if popup_root != root_surface { + continue; + } + + if let Some(parent) = popup.get_parent().map(|parent| parent.surface.clone()) { + let mut parent_root = parent; + while let Some(next) = get_parent(&parent_root) { + parent_root = next; + } + + if let Some(output) = self.niri.output_for_root(&parent_root) { + self.niri.queue_redraw(&output.clone()); + return; + } + } + } + // This might be a layer-shell surface. if self.layer_shell_handle_commit(surface) { return; diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 263c965402..3b1fbeb0ce 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -32,6 +32,9 @@ use smithay::wayland::fractional_scale::FractionalScaleHandler; use smithay::wayland::idle_inhibit::IdleInhibitHandler; use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState}; use smithay::wayland::input_method::{InputMethodHandler, PopupSurface}; +use smithay::wayland::input_method_v1::{ + InputMethodV1Handler, PopupSurface as InputMethodPopupSurfaceV1, +}; use smithay::wayland::keyboard_shortcuts_inhibit::{ KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor, }; @@ -64,11 +67,12 @@ use smithay::{ delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf, delegate_drm_lease, delegate_ext_data_control, delegate_fractional_scale, delegate_idle_inhibit, delegate_idle_notify, delegate_input_method_manager, - delegate_keyboard_shortcuts_inhibit, delegate_output, delegate_pointer_constraints, - delegate_pointer_gestures, delegate_presentation, delegate_primary_selection, - delegate_relative_pointer, delegate_seat, delegate_security_context, delegate_session_lock, - delegate_single_pixel_buffer, delegate_tablet_manager, delegate_text_input_manager, - delegate_viewporter, delegate_virtual_keyboard_manager, delegate_xdg_activation, + delegate_input_method_manager_v1, delegate_keyboard_shortcuts_inhibit, delegate_output, + delegate_pointer_constraints, delegate_pointer_gestures, delegate_presentation, + delegate_primary_selection, delegate_relative_pointer, delegate_seat, + delegate_security_context, delegate_session_lock, delegate_single_pixel_buffer, + delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter, + delegate_virtual_keyboard_manager, delegate_xdg_activation, }; pub use crate::handlers::xdg_shell::KdeDecorationsModeState; @@ -249,14 +253,108 @@ impl InputMethodHandler for State { } fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { + let mut root = parent.clone(); + while let Some(next) = get_parent(&root) { + root = next; + } + self.niri .layout - .find_window_and_output(parent) + .find_window_and_output(&root) .map(|(mapped, _)| mapped.window.geometry()) .unwrap_or_default() } } +impl InputMethodV1Handler for State { + fn new_popup(&mut self, surface: InputMethodPopupSurfaceV1) { + if let Some(parent) = surface.get_parent().map(|parent| parent.surface.clone()) { + let mut root = parent; + while let Some(next) = get_parent(&root) { + root = next; + } + + if let Some(output) = self.niri.output_for_root(&root) { + let scale = output.current_scale(); + let transform = output.current_transform(); + let wl_surface = surface.wl_surface(); + with_states(wl_surface, |data| { + send_scale_transform(wl_surface, data, scale, transform); + }); + } + } + + self.niri + .input_method_v1_popups + .retain(|p| p.alive() && p.wl_surface() != surface.wl_surface()); + self.niri.input_method_v1_popups.push(surface); + self.niri.queue_redraw_all(); + } + + fn popup_repositioned(&mut self, surface: InputMethodPopupSurfaceV1) { + self.niri + .input_method_v1_popups + .retain(|p| p.alive() && p.wl_surface() != surface.wl_surface()); + self.niri.input_method_v1_popups.push(surface); + self.niri.queue_redraw_all(); + } + + fn dismiss_popup(&mut self, surface: InputMethodPopupSurfaceV1) { + self.niri + .input_method_v1_popups + .retain(|p| p.wl_surface() != surface.wl_surface()); + self.niri.queue_redraw_all(); + } + + fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { + self.input_method_parent_geometry(parent) + } +} + +impl State { + fn input_method_parent_geometry(&self, parent: &WlSurface) -> Rectangle { + let mut root = parent.clone(); + while let Some(next) = get_parent(&root) { + root = next; + } + + let Some((mapped, output)) = self.niri.layout.find_window_and_output(&root) else { + return Rectangle::default(); + }; + + let window_geometry = mapped.window.geometry(); + let window = mapped.window.clone(); + let Some(output) = output.cloned() else { + return window_geometry; + }; + + let Some(mon) = self.niri.layout.monitor_for_output(&output) else { + return window_geometry; + }; + let Some((ws, ws_geo)) = mon + .workspaces_with_render_geo() + .find(|(ws, _)| ws.has_window(&window)) + else { + return window_geometry; + }; + let Some((tile, tile_offset, _)) = ws + .tiles_with_render_positions() + .find(|(tile, _, _)| tile.window().window == window) + else { + return window_geometry; + }; + + let zoom = mon.overview_zoom(); + let Some(output_geo) = self.niri.global_space.output_geometry(&output) else { + return window_geometry; + }; + + let loc = + output_geo.loc.to_f64() + ws_geo.loc + tile_offset.upscale(zoom) + tile.window_loc(); + Rectangle::new(loc.to_i32_round(), window_geometry.size) + } +} + impl KeyboardShortcutsInhibitHandler for State { fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState { &mut self.niri.keyboard_shortcuts_inhibit_state @@ -278,6 +376,7 @@ impl KeyboardShortcutsInhibitHandler for State { } delegate_input_method_manager!(State); +delegate_input_method_manager_v1!(State); delegate_keyboard_shortcuts_inhibit!(State); delegate_virtual_keyboard_manager!(State); diff --git a/src/niri.rs b/src/niri.rs index d84c390abf..b9cac0494b 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -71,7 +71,7 @@ use smithay::utils::{ Transform, SERIAL_COUNTER, }; use smithay::wayland::compositor::{ - with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler, + get_parent, with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler, CompositorState, HookId, SurfaceData, TraversalAction, }; use smithay::wayland::cursor_shape::CursorShapeManagerState; @@ -80,6 +80,8 @@ use smithay::wayland::fractional_scale::FractionalScaleManagerState; use smithay::wayland::idle_inhibit::IdleInhibitManagerState; use smithay::wayland::idle_notify::IdleNotifierState; use smithay::wayland::input_method::InputMethodManagerState; +use smithay::wayland::input_method_v1::InputMethodV1ManagerState; +use smithay::wayland::input_method_v1::PopupSurface as InputMethodPopupSurfaceV1; use smithay::wayland::keyboard_shortcuts_inhibit::{ KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor, }; @@ -286,6 +288,7 @@ pub struct Niri { pub tablet_state: TabletManagerState, pub text_input_state: TextInputManagerState, pub input_method_state: InputMethodManagerState, + pub input_method_v1_state: InputMethodV1ManagerState, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub virtual_keyboard_state: VirtualKeyboardManagerState, pub virtual_pointer_state: VirtualPointerManagerState, @@ -299,6 +302,7 @@ pub struct Niri { pub wlr_data_control_state: WlrDataControlState, pub ext_data_control_state: ExtDataControlState, pub popups: PopupManager, + pub input_method_v1_popups: Vec, pub popup_grab: Option, pub presentation_state: PresentationState, pub security_context_state: SecurityContextState, @@ -2264,6 +2268,8 @@ impl Niri { let text_input_state = TextInputManagerState::new::(&display_handle); let input_method_state = InputMethodManagerState::new::(&display_handle, client_is_unrestricted); + let input_method_v1_state = + InputMethodV1ManagerState::new::(&display_handle, client_is_unrestricted); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(&display_handle); let virtual_keyboard_state = @@ -2466,6 +2472,7 @@ impl Niri { xdg_foreign_state, text_input_state, input_method_state, + input_method_v1_state, keyboard_shortcuts_inhibit_state, virtual_keyboard_state, virtual_pointer_state, @@ -2485,6 +2492,7 @@ impl Niri { wlr_data_control_state, ext_data_control_state, popups: PopupManager::default(), + input_method_v1_popups: Vec::new(), popup_grab: None, suppressed_keys: HashSet::new(), suppressed_buttons: HashSet::new(), @@ -4068,6 +4076,35 @@ impl Niri { self.render_pointer(renderer, output, &mut |elem| push(elem.into())); } + // input-method-v1 popups (candidate window / panel), below the pointer. + for popup in self.input_method_v1_popups.iter().filter(|p| p.alive()) { + let Some(parent) = popup.get_parent() else { + continue; + }; + + let mut root = parent.surface.clone(); + while let Some(next) = get_parent(&root) { + root = next; + } + + if self.output_for_root(&root) != Some(output) { + continue; + } + + let location = (parent.location.loc + popup.location()) + .to_f64() + .to_physical_precise_round(output_scale); + push_elements_from_surface_tree( + renderer, + popup.wl_surface(), + location, + output_scale, + 1., + Kind::ScanoutCandidate, + &mut |elem| push(elem.into()), + ); + } + // Next, the screen transition texture. { let state = self.output_state.get(output).unwrap();