use core::ffi::{CStr, c_int, c_void};
use core::ptr;
use crcbl_core::SurfaceTarget;
use super::super::ffi::{self, WlArgument, WlDisplay, WlMessage, WlProxy};
use super::super::protocol::input_method::{zwp_input_method_manager_v2, zwp_input_method_v2};
use super::super::protocol::wayland::{wl_display, wl_registry, wl_seat};
use super::ignore;
use crate::{Shell, ShellError, WindowId};
struct State {
registry: *mut WlProxy,
seat: *mut WlProxy,
manager: *mut WlProxy,
method: *mut WlProxy,
pending_active: bool,
active: bool,
pending_content: (u32, u32),
content: (u32, u32),
done: u32,
unavailable: bool,
}
unsafe extern "C" fn bind(
user_data: *const c_void,
_target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<State>().cast_mut() };
let event = unsafe { wl_registry::decode_event(opcode, &*args.cast_const()) };
let Some(wl_registry::Event::Global {
name, interface, ..
}) = event
else {
return 0;
};
unsafe {
if interface == wl_seat::NAME && state.seat.is_null() {
state.seat = wl_registry::bind(state.registry, name, &wl_seat::INTERFACE, 1);
} else if interface == zwp_input_method_manager_v2::NAME && state.manager.is_null() {
state.manager = wl_registry::bind(
state.registry,
name,
&zwp_input_method_manager_v2::INTERFACE,
1,
);
}
}
0
}
unsafe extern "C" fn follow(
user_data: *const c_void,
_target: *mut c_void,
opcode: u32,
_message: *const WlMessage,
args: *mut WlArgument,
) -> c_int {
let state = unsafe { &mut *user_data.cast::<State>().cast_mut() };
match unsafe { zwp_input_method_v2::decode_event(opcode, &*args.cast_const()) } {
Some(zwp_input_method_v2::Event::Activate) => state.pending_active = true,
Some(zwp_input_method_v2::Event::Deactivate) => state.pending_active = false,
Some(zwp_input_method_v2::Event::ContentType { hint, purpose }) => {
state.pending_content = (hint, purpose);
}
Some(zwp_input_method_v2::Event::Done) => {
state.done = state.done.wrapping_add(1);
state.active = state.pending_active;
state.content = state.pending_content;
}
Some(zwp_input_method_v2::Event::Unavailable) => state.unavailable = true,
_ => {}
}
0
}
pub struct InputMethod {
lib: &'static ffi::Lib,
display: *mut WlDisplay,
state: Box<State>,
}
impl core::fmt::Debug for InputMethod {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("InputMethod")
.field("active", &self.state.active)
.field("done", &self.state.done)
.finish()
}
}
impl InputMethod {
pub fn attach(shell: &dyn Shell, window: WindowId) -> Result<Self, ShellError> {
let SurfaceTarget::Wayland { display, .. } = shell.surface_target(window)? else {
return Err(ShellError::Backend(
"not a Wayland window; this helper is only for the Wayland backend".to_string(),
));
};
let lib = ffi::load().map_err(|detail| ShellError::Backend(detail.to_string()))?;
let display: *mut WlDisplay = display.as_ptr().cast();
let mut state = Box::new(State {
registry: ptr::null_mut(),
seat: ptr::null_mut(),
manager: ptr::null_mut(),
method: ptr::null_mut(),
pending_active: false,
active: false,
pending_content: (0, 0),
content: (0, 0),
done: 0,
unavailable: false,
});
let user_data: *const c_void = ptr::from_mut(state.as_mut()).cast();
unsafe {
state.registry = wl_display::get_registry(ffi::display_as_proxy(display));
if state.registry.is_null() {
return Err(ShellError::Backend("wl_display.get_registry".to_string()));
}
(lib.proxy_add_dispatcher)(state.registry, bind, user_data, ptr::null_mut());
if (lib.display_roundtrip)(display) < 0 {
return Err(ShellError::Backend("roundtrip failed".to_string()));
}
}
let mut method = Self {
lib,
display,
state,
};
if method.state.seat.is_null() || method.state.manager.is_null() {
return Err(ShellError::Backend(
"the compositor has no wl_seat or zwp_input_method_manager_v2".to_string(),
));
}
unsafe {
(lib.proxy_add_dispatcher)(method.state.seat, ignore, ptr::null(), ptr::null_mut());
method.state.method = zwp_input_method_manager_v2::get_input_method(
method.state.manager,
method.state.seat,
);
if method.state.method.is_null() {
return Err(ShellError::Backend(
"zwp_input_method_manager_v2.get_input_method".to_string(),
));
}
(lib.proxy_add_dispatcher)(method.state.method, follow, user_data, ptr::null_mut());
(lib.display_roundtrip)(display);
}
if method.state.unavailable {
return Err(ShellError::Backend(
"the compositor already has an input method".to_string(),
));
}
method.flush();
Ok(method)
}
#[must_use]
pub fn is_active(&self) -> bool {
self.state.active
}
#[must_use]
pub fn content_type(&self) -> (u32, u32) {
self.state.content
}
pub fn preedit(&self, text: &CStr, cursor: i32) {
unsafe {
zwp_input_method_v2::set_preedit_string(self.state.method, text, cursor, cursor);
zwp_input_method_v2::commit(self.state.method, self.state.done);
}
self.flush();
}
pub fn commit(&self, text: &CStr) {
unsafe {
zwp_input_method_v2::commit_string(self.state.method, text);
zwp_input_method_v2::commit(self.state.method, self.state.done);
}
self.flush();
}
fn flush(&self) {
unsafe { (self.lib.display_flush)(self.display) };
}
}
impl Drop for InputMethod {
fn drop(&mut self) {
unsafe {
if !self.state.method.is_null() {
zwp_input_method_v2::destroy(self.state.method);
}
if !self.state.manager.is_null() {
zwp_input_method_manager_v2::destroy(self.state.manager);
}
if !self.state.seat.is_null() {
(self.lib.proxy_destroy)(self.state.seat);
}
(self.lib.proxy_destroy)(self.state.registry);
(self.lib.display_flush)(self.display);
}
}
}