Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/text_input.rs (6K)
1 // SPDX-FileCopyrightText: © 2021 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::seat::Seat;
7
8 #[repr(C)]
9 pub struct TextInput {
10 pub link: ffi::wl_list,
11 pub wlr_text_input: *mut ffi::wlr_text_input_v3,
12
13 pub enable: ffi::wl_listener,
14 pub commit: ffi::wl_listener,
15 pub disable: ffi::wl_listener,
16 pub destroy: ffi::wl_listener,
17 }
18
19 unsafe fn connect_listener(
20 signal: *mut ffi::wl_signal,
21 listener: *mut ffi::wl_listener,
22 callback: unsafe extern "C" fn(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void),
23 ) {
24 let wl_lis = listener as *mut WlListener;
25 (*wl_lis).notify = Some(callback);
26 wl_signal_add(signal, listener);
27 }
28
29 unsafe fn wl_listener_remove_safe(listener: *mut ffi::wl_listener) {
30 let prev = (*listener).link.prev;
31 let next = (*listener).link.next;
32 if !prev.is_null() && !next.is_null() && prev != listener as *mut ffi::wl_list && next != listener as *mut ffi::wl_list {
33 ffi::wl_list_remove(&mut (*listener).link);
34 (*listener).link.prev = std::ptr::null_mut();
35 (*listener).link.next = std::ptr::null_mut();
36 }
37 }
38
39 impl TextInput {
40 pub unsafe fn create(wlr_text_input: *mut ffi::wlr_text_input_v3) -> Result<(), &'static str> {
41 let wlr_seat = (*wlr_text_input).seat;
42 let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat;
43 if seat.is_null() {
44 return Err("Seat is null");
45 }
46
47 let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name(wlr_seat))
48 .to_str()
49 .unwrap_or("unknown");
50 log::debug!("new text input on seat {}", seat_name);
51
52 let mut text_input = Box::new(TextInput {
53 link: std::mem::zeroed(),
54 wlr_text_input,
55 enable: std::mem::zeroed(),
56 commit: std::mem::zeroed(),
57 disable: std::mem::zeroed(),
58 destroy: std::mem::zeroed(),
59 });
60
61 ffi::wl_list_init(&mut text_input.link);
62
63 let raw = Box::into_raw(text_input);
64
65 // Append to seat.relay.text_inputs
66 let text_inputs_list = &mut (*seat).relay.text_inputs as *mut ffi::wl_list as *mut WlList;
67 crate::server::wl_list_insert((*text_inputs_list).prev, &mut (*raw).link as *mut ffi::wl_list as *mut WlList);
68
69 connect_listener(&mut (*wlr_text_input).events.enable, &mut (*raw).enable, handle_enable);
70 connect_listener(&mut (*wlr_text_input).events.commit, &mut (*raw).commit, handle_commit);
71 connect_listener(&mut (*wlr_text_input).events.disable, &mut (*raw).disable, handle_disable);
72 connect_listener(&mut (*wlr_text_input).events.destroy, &mut (*raw).destroy, handle_destroy);
73
74 // A client that binds its text input after its surface took focus
75 // (cce-ui binds lazily) is entered now; `InputRelay::focus` only
76 // enters on a focus change.
77 let focused = (*seat).focused.surface();
78 if !focused.is_null()
79 && ffi::wl_resource_get_client(ffi::river_wlr_surface_get_resource(focused))
80 == ffi::wl_resource_get_client((*wlr_text_input).resource)
81 {
82 ffi::wlr_text_input_v3_send_enter(wlr_text_input, focused);
83 }
84
85 Ok(())
86 }
87 }
88
89 unsafe extern "C" fn handle_enable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
90 let text_input = crate::container_of!(listener, TextInput, enable);
91 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
92 if seat.is_null() {
93 return;
94 }
95
96 if (*(*text_input).wlr_text_input).focused_surface.is_null() {
97 log::error!("client requested to enable text input without focus, ignoring request");
98 return;
99 }
100
101 if !(*seat).relay.text_input.is_null() {
102 if text_input != (*seat).relay.text_input {
103 log::error!("client requested to enable more than one text input on a single seat, ignoring request");
104 return;
105 }
106 }
107
108 (*seat).relay.text_input = text_input;
109 (*seat).relay.osk.field_active();
110
111 let input_method = (*seat).relay.input_method;
112 if !input_method.is_null() {
113 ffi::wlr_input_method_v2_send_activate(input_method);
114 (*seat).relay.send_input_method_state();
115 }
116 }
117
118 unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
119 let text_input = crate::container_of!(listener, TextInput, commit);
120 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
121 if seat.is_null() {
122 return;
123 }
124
125 if (*seat).relay.text_input != text_input {
126 log::error!("inactive text input tried to commit an update, client bug?");
127 return;
128 }
129
130 (*seat).relay.osk.field_active();
131 if !(*seat).relay.input_method.is_null() {
132 (*seat).relay.send_input_method_state();
133 }
134 }
135
136 unsafe extern "C" fn handle_disable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
137 let text_input = crate::container_of!(listener, TextInput, disable);
138 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
139 if seat.is_null() {
140 return;
141 }
142
143 if (*seat).relay.text_input == text_input {
144 (*seat).relay.disable_text_input();
145 }
146 }
147
148 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
149 let text_input = crate::container_of!(listener, TextInput, destroy);
150 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
151 if seat.is_null() {
152 return;
153 }
154
155 if (*seat).relay.text_input == text_input {
156 (*seat).relay.disable_text_input();
157 }
158
159 wl_listener_remove_safe(&mut (*text_input).enable);
160 wl_listener_remove_safe(&mut (*text_input).commit);
161 wl_listener_remove_safe(&mut (*text_input).disable);
162 wl_listener_remove_safe(&mut (*text_input).destroy);
163
164 ffi::wl_list_remove(&mut (*text_input).link);
165
166 let _ = Box::from_raw(text_input);
167 }