Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/text_input.rs (5.4K)
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 Ok(())
75 }
76 }
77
78 unsafe extern "C" fn handle_enable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
79 let text_input = crate::container_of!(listener, TextInput, enable);
80 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
81 if seat.is_null() {
82 return;
83 }
84
85 if (*(*text_input).wlr_text_input).focused_surface.is_null() {
86 log::error!("client requested to enable text input without focus, ignoring request");
87 return;
88 }
89
90 if !(*seat).relay.text_input.is_null() {
91 if text_input != (*seat).relay.text_input {
92 log::error!("client requested to enable more than one text input on a single seat, ignoring request");
93 return;
94 }
95 }
96
97 (*seat).relay.text_input = text_input;
98
99 let input_method = (*seat).relay.input_method;
100 if !input_method.is_null() {
101 ffi::wlr_input_method_v2_send_activate(input_method);
102 (*seat).relay.send_input_method_state();
103 }
104 }
105
106 unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
107 let text_input = crate::container_of!(listener, TextInput, commit);
108 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
109 if seat.is_null() {
110 return;
111 }
112
113 if (*seat).relay.text_input != text_input {
114 log::error!("inactive text input tried to commit an update, client bug?");
115 return;
116 }
117
118 if !(*seat).relay.input_method.is_null() {
119 (*seat).relay.send_input_method_state();
120 }
121 }
122
123 unsafe extern "C" fn handle_disable(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
124 let text_input = crate::container_of!(listener, TextInput, disable);
125 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
126 if seat.is_null() {
127 return;
128 }
129
130 if (*seat).relay.text_input == text_input {
131 (*seat).relay.disable_text_input();
132 }
133 }
134
135 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
136 let text_input = crate::container_of!(listener, TextInput, destroy);
137 let seat = ffi::river_wlr_seat_get_data((*(*text_input).wlr_text_input).seat) as *mut Seat;
138 if seat.is_null() {
139 return;
140 }
141
142 if (*seat).relay.text_input == text_input {
143 (*seat).relay.disable_text_input();
144 }
145
146 wl_listener_remove_safe(&mut (*text_input).enable);
147 wl_listener_remove_safe(&mut (*text_input).commit);
148 wl_listener_remove_safe(&mut (*text_input).disable);
149 wl_listener_remove_safe(&mut (*text_input).destroy);
150
151 ffi::wl_list_remove(&mut (*text_input).link);
152
153 let _ = Box::from_raw(text_input);
154 }