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 }