git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

src/server/input_popup.rs (8.2K)

  1 // SPDX-FileCopyrightText: © 2024 The River Developers
  2 // SPDX-License-Identifier: GPL-3.0-only
  3 
  4 use crate::ffi;
  5 use crate::server::{WlListener, wl_signal_add, WlList};
  6 use crate::input_relay::InputRelay;
  7 use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
  8 
  9 #[repr(C)]
 10 pub struct InputPopup {
 11     pub link: ffi::wl_list,
 12     pub input_relay: *mut InputRelay,
 13     pub wlr_popup: *mut ffi::wlr_input_popup_surface_v2,
 14     pub surface_tree: *mut ffi::wlr_scene_tree,
 15 
 16     pub destroy: ffi::wl_listener,
 17     pub map: ffi::wl_listener,
 18     pub unmap: ffi::wl_listener,
 19     pub commit: ffi::wl_listener,
 20 }
 21 
 22 unsafe fn connect_listener(
 23     signal: *mut ffi::wl_signal,
 24     listener: *mut ffi::wl_listener,
 25     callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
 26 ) {
 27     let wl_lis = listener as *mut WlListener;
 28     (*wl_lis).notify = Some(callback);
 29     wl_signal_add(signal, listener);
 30 }
 31 
 32 unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
 33     let prev = (*listener).link.prev;
 34     let next = (*listener).link.next;
 35     if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
 36         ffi::wl_list_remove(&mut (*listener).link);
 37         (*listener).link.prev = std::ptr::null_mut();
 38         (*listener).link.next = std::ptr::null_mut();
 39     }
 40 }
 41 
 42 impl InputPopup {
 43     pub unsafe fn create(
 44         wlr_popup: *mut ffi::wlr_input_popup_surface_v2,
 45         input_relay: *mut InputRelay,
 46     ) -> Result<(), &'static str> {
 47         let server = (*(*input_relay).seat).server;
 48         let hidden_tree = (*server).scene.hidden_tree;
 49 
 50         let surface_tree = ffi::wlr_scene_subsurface_tree_create(hidden_tree, (*wlr_popup).surface);
 51         if surface_tree.is_null() {
 52             return Err("Failed to create subsurface tree for input popup");
 53         }
 54 
 55         let mut input_popup = Box::new(InputPopup {
 56             link: std::mem::zeroed(),
 57             input_relay,
 58             wlr_popup,
 59             surface_tree,
 60             destroy: std::mem::zeroed(),
 61             map: std::mem::zeroed(),
 62             unmap: std::mem::zeroed(),
 63             commit: std::mem::zeroed(),
 64         });
 65 
 66         ffi::wl_list_init(&mut input_popup.link);
 67 
 68         let raw = Box::into_raw(input_popup);
 69 
 70         // Append to input_relay.input_popups
 71         let popups_list = &mut (*input_relay).input_popups as *mut ffi::wl_list as *mut WlList;
 72         crate::server::wl_list_insert((*popups_list).prev, &mut (*raw).link as *mut ffi::wl_list as *mut WlList);
 73 
 74         connect_listener(&mut (*wlr_popup).events.destroy, &mut (*raw).destroy, handle_destroy);
 75         connect_listener(
 76             ffi::river_wlr_surface_get_map_signal((*wlr_popup).surface),
 77             &mut (*raw).map,
 78             handle_map,
 79         );
 80         connect_listener(
 81             ffi::river_wlr_surface_get_unmap_signal((*wlr_popup).surface),
 82             &mut (*raw).unmap,
 83             handle_unmap,
 84         );
 85         connect_listener(
 86             ffi::river_wlr_surface_get_commit_signal((*wlr_popup).surface),
 87             &mut (*raw).commit,
 88             handle_commit,
 89         );
 90 
 91         (*raw).update();
 92 
 93         Ok(())
 94     }
 95 
 96     pub unsafe fn update(&mut self) {
 97         let text_input = (*self.input_relay).text_input;
 98         if text_input.is_null() {
 99             let server = (*(*self.input_relay).seat).server;
100             let hidden_tree = (*server).scene.hidden_tree;
101             ffi::wlr_scene_node_reparent(
102                 self.surface_tree as *mut ffi::wlr_scene_node,
103                 hidden_tree,
104             );
105             return;
106         }
107 
108         if !ffi::river_wlr_surface_is_mapped((*self.wlr_popup).surface) {
109             return;
110         }
111 
112         let focused_surface = (*(*text_input).wlr_text_input).focused_surface;
113         if focused_surface.is_null() {
114             return;
115         }
116 
117         assert_eq!(ffi::wlr_surface_get_root_surface(focused_surface), focused_surface);
118 
119         let focused = match SceneNodeData::from_surface(focused_surface) {
120             Some(f) => f,
121             None => return,
122         };
123 
124         let server = (*(*self.input_relay).seat).server;
125 
126         let popup_tree = match focused.data {
127             SceneNodeDataVal::Window(window) => (*window).popup_tree,
128             SceneNodeDataVal::ShellSurface(shell_surface) => (*shell_surface).popup_tree,
129             SceneNodeDataVal::LockSurface(_) => (*server).scene.layers.popups,
130             SceneNodeDataVal::LayerSurface(layer_surface) => (*layer_surface).popup_tree,
131             SceneNodeDataVal::OverrideRedirect(_) => panic!("Xwayland doesn't use text-input protocol"),
132         };
133 
134         ffi::wlr_scene_node_reparent(self.surface_tree as *mut ffi::wlr_scene_node, popup_tree);
135 
136         // cursor_rectangle features check: check if WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is active
137         let active_features = (*(*text_input).wlr_text_input).active_features;
138         let feature_cursor_rect = ffi::wlr_text_input_v3_features_WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE;
139         if (active_features & feature_cursor_rect) == 0 {
140             ffi::wlr_scene_node_set_position(self.surface_tree as *mut ffi::wlr_scene_node, 0, 0);
141             return;
142         }
143 
144         let mut focused_x: std::os::raw::c_int = 0;
145         let mut focused_y: std::os::raw::c_int = 0;
146         ffi::wlr_scene_node_coords(focused.node, &mut focused_x, &mut focused_y);
147 
148         let mut cursor_box = (*(*text_input).wlr_text_input).current.cursor_rectangle;
149 
150         let wlr_output = (*server).om.output_at(
151             (focused_x + cursor_box.x) as f64,
152             (focused_y + cursor_box.y) as f64,
153         );
154         if wlr_output.is_null() {
155             return;
156         }
157 
158         let mut output_box = ffi::wlr_box {
159             x: 0,
160             y: 0,
161             width: 0,
162             height: 0,
163         };
164         ffi::wlr_output_layout_get_box((*server).om.output_layout, wlr_output, &mut output_box);
165 
166         cursor_box.x += focused_x - output_box.x;
167         cursor_box.y += focused_y - output_box.y;
168 
169         let popup_width = ffi::river_wlr_surface_get_width((*self.wlr_popup).surface);
170         let popup_height = ffi::river_wlr_surface_get_height((*self.wlr_popup).surface);
171 
172         let popup_x = if output_box.width - cursor_box.x >= popup_width {
173             cursor_box.x
174         } else {
175             cursor_box.x + cursor_box.width - popup_width
176         };
177 
178         let popup_y = if output_box.height - (cursor_box.y + cursor_box.height) >= popup_height {
179             cursor_box.y + cursor_box.height
180         } else {
181             cursor_box.y - popup_height
182         };
183 
184         ffi::wlr_scene_node_set_position(
185             self.surface_tree as *mut ffi::wlr_scene_node,
186             popup_x - focused_x + output_box.x,
187             popup_y - focused_y + output_box.y,
188         );
189 
190         cursor_box.x -= popup_x;
191         cursor_box.y -= popup_y;
192         ffi::wlr_input_popup_surface_v2_send_text_input_rectangle(self.wlr_popup, &mut cursor_box);
193     }
194 }
195 
196 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
197     let input_popup = crate::container_of!(listener, InputPopup, destroy);
198 
199     wl_listener_remove_safe(&mut (*input_popup).destroy);
200     wl_listener_remove_safe(&mut (*input_popup).map);
201     wl_listener_remove_safe(&mut (*input_popup).unmap);
202     wl_listener_remove_safe(&mut (*input_popup).commit);
203 
204     ffi::wl_list_remove(&mut (*input_popup).link);
205 
206     let _ = Box::from_raw(input_popup);
207 }
208 
209 unsafe extern "C" fn handle_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
210     let input_popup = crate::container_of!(listener, InputPopup, map);
211     (*input_popup).update();
212 }
213 
214 unsafe extern "C" fn handle_unmap(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
215     let input_popup = crate::container_of!(listener, InputPopup, unmap);
216     let server = (*(*(*input_popup).input_relay).seat).server;
217     let hidden_tree = (*server).scene.hidden_tree;
218 
219     ffi::wlr_scene_node_reparent(
220         (*input_popup).surface_tree as *mut ffi::wlr_scene_node,
221         hidden_tree,
222     );
223 }
224 
225 unsafe extern "C" fn handle_commit(
226     listener: *mut ffi::wl_listener,
227     _data: *mut std::ffi::c_void,
228 ) {
229     let input_popup = crate::container_of!(listener, InputPopup, commit);
230     (*input_popup).update();
231 }