diff --git a/Cargo.toml b/Cargo.toml index b10ef411bc1..c7a1c3d6073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["bytemuck", "x11-dl", "x11rb", "mio", "percent-encoding"] +x11 = ["bytemuck", "x11-dl", "x11rb", "mio", "percent-encoding", "xim"] wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] @@ -115,6 +115,7 @@ wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], opt wayland-commons = { version = "0.29.5", optional = true } x11-dl = { version = "2.18.5", optional = true } x11rb = { version = "0.11.0", features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } +xim = { git = "https://github.com/notgull/xim-rs.git", branch = "caret-preedit", features = ["client", "x11rb-client", "x11rb-xcb"], optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 2705143efd9..3986e55a265 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,9 +1,4 @@ -use std::{ - cell::RefCell, - collections::{HashMap, VecDeque}, - rc::Rc, - sync::Arc, -}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use x11rb::{ connection::Connection, errors::ConnectionError, @@ -18,13 +13,16 @@ use libc::c_uint; use super::{ atoms::*, - events, ffi, fp1616, fp3232, get_xtarget, mkdid, mkwid, monitor, + events, ffi, fp1616, fp3232, get_xtarget, + ime::{ImeEvent, ImeRequest}, + mkdid, mkwid, monitor, util::{self, PlErrorExt}, Device, DeviceId, DeviceInfo, Dnd, DndState, ScrollOrientation, UnownedWindow, WindowId, }; use crate::event::{ ElementState::{Pressed, Released}, + Ime, MouseButton::{Left, Middle, Other, Right}, MouseScrollDelta::LineDelta, Touch, @@ -48,8 +46,6 @@ const KEYCODE_OFFSET: u8 = 8; pub(super) struct EventProcessor { pub(super) dnd: Dnd, - //pub(super) ime_receiver: ImeReceiver, - //pub(super) ime_event_receiver: ImeEventReceiver, pub(super) devices: RefCell>, pub(super) target: Rc>, pub(super) mod_keymap: ModifierKeymap, @@ -60,9 +56,6 @@ pub(super) struct EventProcessor { // Currently focused window belonging to this process pub(super) active_window: Option, pub(super) is_composing: bool, - - /// A queue containing events that we have read from the X server but have not yet processed. - pub(super) event_queue: VecDeque, } impl EventProcessor { @@ -104,9 +97,12 @@ impl EventProcessor { /// /// Returns `true` if there are events in the queue, `false` otherwise. pub(super) fn poll(&mut self) -> bool { + let wt = get_xtarget(&self.target); + let mut event_queue = wt.xconn.event_queue.lock().unwrap(); + loop { // If we have events in the queue, we need to process them. - if !self.event_queue.is_empty() { + if !event_queue.is_empty() { return true; } @@ -114,7 +110,7 @@ impl EventProcessor { let wt = get_xtarget(&self.target); match wt.xconn.connection.poll_for_event() { Ok(Some(event)) => { - self.event_queue.push_back(event); + event_queue.push_back(event); } Ok(None) => { // No events in the queue, and no events on the connection. @@ -134,12 +130,13 @@ impl EventProcessor { } pub(super) fn poll_one_event(&mut self) -> Result, ConnectionError> { + let wt = get_xtarget(&self.target); + // If we previously polled and found an event, return it. - if let Some(event) = self.event_queue.pop_front() { + if let Some(event) = wt.xconn.event_queue.lock().unwrap().pop_front() { return Ok(Some(event)); } - let wt = get_xtarget(&self.target); wt.xconn.connection.poll_for_event() } @@ -149,7 +146,12 @@ impl EventProcessor { { let wt = get_xtarget(&self.target); - // TODO: Filter IME events. + // Filter out any IME events. + if let Some(ime_data) = wt.ime.as_ref() { + if ime_data.filter_event(xev) { + return; + } + } // We can't call a `&mut self` method because of the above borrow, // so we use this macro for repeated modifier state updates. @@ -177,7 +179,7 @@ impl EventProcessor { match xev { X11Event::MappingNotify(mapping) => { if matches!(mapping.request, Mapping::MODIFIER | Mapping::KEYBOARD) { - // TODO: IME + // TODO: Use XKB to get more accurate keymap info. // Update modifier keymap. self.mod_keymap @@ -550,12 +552,10 @@ impl EventProcessor { // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. - /* - wt.ime - .borrow_mut() - .remove_context(window as _) - .expect("Failed to destroy input context"); - */ + if let Some(ime) = wt.ime.as_ref() { + ime.remove_context(window as _) + .expect("Failed to destroy input context"); + } callback(Event::WindowEvent { window_id, @@ -633,6 +633,7 @@ impl EventProcessor { } /* + TODO: XKB if state == Pressed { let written = if let Some(ic) = wt.ime.borrow().get_context(window as _) { wt.xconn.lookup_utf8(ic, xkev) @@ -923,12 +924,10 @@ impl EventProcessor { } X11Event::XinputFocusIn(xev) => { - /* - wt.ime - .borrow_mut() - .focus(xev.event as _) - .expect("Failed to focus input context"); - */ + if let Some(ime) = wt.ime.as_ref() { + ime.focus_window(&wt.xconn, xev.event as _) + .expect("Failed to focus input context"); + } let modifiers = ModifiersState::from_x11(&xev.mods); @@ -994,12 +993,10 @@ impl EventProcessor { return; } - /* - wt.ime - .borrow_mut() - .unfocus(xev.event as _) - .expect("Failed to unfocus input context"); - */ + if let Some(ime) = wt.ime.as_ref() { + ime.unfocus_window(&wt.xconn, xev.event as _) + .expect("Failed to focus input context"); + } if self.active_window.take() == Some(xev.event) { let window_id = mkwid(xev.event); @@ -1281,69 +1278,69 @@ impl EventProcessor { } } } - _ => {} } // Handle IME requests. - - /* - if let Ok(request) = self.ime_receiver.try_recv() { - let mut ime = wt.ime.borrow_mut(); - match request { - ImeRequest::Position(window_id, x, y) => { - ime.send_xim_spot(window_id, x, y); - } - ImeRequest::Allow(window_id, allowed) => { - ime.set_ime_allowed(window_id, allowed); + if let Some(ime) = wt.ime.as_ref() { + if let Some(ime_request) = wt.ime_receiver.try_recv().ok() { + match ime_request { + ImeRequest::Position(window_id, x, y) => { + ime.set_spot(&wt.xconn, window_id, x, y) + .expect("Failed to set IME spot"); + } + ImeRequest::Allow(window_id, allowed) => { + ime.set_ime_allowed(&wt.xconn, window_id, allowed) + .expect("Failed to set IME allowed"); + } } } - } - let (window, event) = match self.ime_event_receiver.try_recv() { - Ok((window, event)) => (window as _, event), - Err(_) => return, - }; + // See if any IME events have occurred. + let (window, event) = match ime.next_event() { + Some((window, event)) => (window, event), + None => return, + }; - match event { - ImeEvent::Enabled => { - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Enabled), - }); - } - ImeEvent::Start => { - self.is_composing = true; - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)), - }); - } - ImeEvent::Update(text, position) => { - if self.is_composing { + match event { + ImeEvent::Enabled => { callback(Event::WindowEvent { window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))), + event: WindowEvent::Ime(Ime::Enabled), + }); + } + ImeEvent::Start => { + self.is_composing = true; + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Preedit("".to_owned(), None)), + }); + } + ImeEvent::Update(text, position) => { + if self.is_composing { + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))), + }); + } + } + ImeEvent::End => { + self.is_composing = false; + // Issue empty preedit on `Done`. + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + }); + } + ImeEvent::Disabled => { + self.is_composing = false; + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::Ime(Ime::Disabled), }); } - } - ImeEvent::End => { - self.is_composing = false; - // Issue empty preedit on `Done`. - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - }); - } - ImeEvent::Disabled => { - self.is_composing = false; - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::Ime(Ime::Disabled), - }); } } - */ } fn handle_pressed_keys( diff --git a/src/platform_impl/linux/x11/ime.rs b/src/platform_impl/linux/x11/ime.rs new file mode 100644 index 00000000000..b3137ca1e26 --- /dev/null +++ b/src/platform_impl/linux/x11/ime.rs @@ -0,0 +1,896 @@ +//! Implementation of input method handling using `xim-rs`. + +use super::XConnection; + +use std::cell::{RefCell, RefMut}; +use std::collections::VecDeque; +use std::ffi::CStr; +use std::fmt; +use std::sync::Arc; + +use xim::x11rb::{HasConnection, X11rbClient}; +use xim::AHashMap; +use xim::{AttributeName, InputStyle, Point}; +use xim::{Client as _, ClientError, ClientHandler}; + +use x11rb::connection::Connection; +use x11rb::protocol::xproto::Window; +use x11rb::protocol::Event; +use x11rb::xcb_ffi::XCBConnection; + +/// Lock the refcell. +/// +/// This exists in case we want to migrate this to a Mutex-based implementation. +macro_rules! lock { + ($mutex:expr) => { + $mutex.borrow_mut() + }; +} + +/// Get the current locale. +fn locale() -> String { + const EN_US: &str = "en_US.UTF-8"; + + // Get the pointer to the current locale. + let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) }; + + // If locale_ptr is null, just default to en_US.UTF-8. + if locale_ptr.is_null() { + return EN_US.to_owned(); + } + + // Convert the pointer to a CStr. + let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) }; + + // Convert the CStr to a String to prevent the result from getting clobbered. + locale_cstr.to_str().unwrap_or(EN_US).to_owned() +} + +impl HasConnection for XConnection { + type Connection = XCBConnection; + + fn conn(&self) -> &Self::Connection { + &self.connection + } +} + +#[derive(Copy, Clone)] +enum Style { + Preedit, + Nothing, + None, +} + +/// A collection of the IME events that can occur. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ImeEvent { + Enabled, + Start, + Update(String, usize), + End, + Disabled, +} + +/// Request to control XIM handler from the window. +pub enum ImeRequest { + /// Set IME spot position for given `window_id`. + Position(Window, i16, i16), + + /// Allow IME input for the given `window_id`. + Allow(Window, bool), +} + +type XimClient = X11rbClient>; + +/// IME-related information. +pub(super) struct ImeData { + /// The XIM client. + client: RefCell, + + /// State of the IME. + inner_data: RefCell, +} + +/// The current IME state. +#[derive(Default)] +struct ClientData { + /// The input method we are currently using. + input_method: Option, + + /// The registered input styles for the IM. + styles: Option<(Style, Style)>, + + /// The list of produced IME events. + events: VecDeque<(Window, ImeEvent)>, + + /// Whether the IME is currently disconnected. + disconnected: bool, + + /// Hash map of input context IDs to their data. + ic_data: AHashMap, + + /// Hash map of window IDs to their input context IDs. + window_data: AHashMap, + + /// Windows that are waiting for an input context. + pending_windows: VecDeque, +} + +/// Per-input context data. +struct IcData { + /// The identifier of the context. + id: u16, + + /// The window that the context is attached to. + window: Window, + + /// The style of the context. + style: Style, + + /// The current "spot" for the context. + spot: Point, + + /// The newly set spot for the context. + new_spot: Option, + + /// The current preedit string. + /// + /// We use a `Vec` here instead of a string because the IME indices operate on chars, + /// not bytes. + text: Vec, + + /// The current cursor position in the preedit string. + cursor: usize, +} + +struct PendingData { + /// The window that the context is attached to. + window: Window, + + /// The style of the context. + style: Style, + + /// The current "spot" for the context. + spot: Point, +} + +impl IcData { + fn available(&self) -> bool { + !matches!(self.style, Style::None) + } +} + +impl ImeData { + /// Create a new `ImeData`. + pub(super) fn new(xconn: &Arc, screen: usize) -> Result { + // IM servers to try, in order: + // - None, which defaults to the environment variable `XMODIFIERS` in xim's impl. + // - "local", which is the default for most IMEs. + // - empty string, which may work in some cases. + let input_methods = [None, Some("local"), Some("")]; + + let client = 'get_client: loop { + let mut last_error = None; + + for im in input_methods { + // Try to create a client. + match XimClient::init(xconn.clone(), screen, im) { + Ok(client) => break 'get_client client, + Err(err) => { + struct ImName(Option<&'static str>); + + impl fmt::Debug for ImName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Some(name) => write!(f, "\"{}\"", name), + None => write!(f, "default input method"), + } + } + } + + log::warn!("Failed to create XIM client for {:?}: {}", ImName(im), &err); + last_error = Some(err); + } + } + } + + return Err(last_error.unwrap_or(ClientError::NoXimServer)); + }; + + Ok(Self { + client: RefCell::new(client), + inner_data: RefCell::new(ClientData { + disconnected: true, + ..Default::default() + }), + }) + } + + /// Run the IME if this event has any relevance to IME. + pub(super) fn filter_event(&self, event: &Event) -> bool { + let mut this = self; + + lock!(self.client) + .filter_event(event, &mut this) + .expect("Failed to filter event") + } + + /// Get the next pending IME event. + pub(super) fn next_event(&self) -> Option<(Window, ImeEvent)> { + lock!(self.inner_data).events.pop_front() + } + + /// Block until we've acted on a new IME event. + pub(super) fn block_for_ime(&self, conn: &XConnection) -> Result<(), ClientError> { + let mut last_event = conn.connection.poll_for_event()?; + + loop { + let mut event_queue = conn.event_queue.lock().unwrap_or_else(|e| e.into_inner()); + + // Check the last event we've seen. + if let Some(last_event) = last_event.as_ref() { + if self.filter_event(last_event) { + return Ok(()); + } + } + + // See if there's anything worth filtering in the event queue. + let mut filtered_any = false; + event_queue.retain(|event| { + if self.filter_event(event) { + filtered_any = true; + false + } else { + true + } + }); + + event_queue.extend(last_event); + if filtered_any { + return Ok(()); + } + + // Wait for a new event. + log::info!("x11(ime): Entering wait for IME event"); + last_event = Some(conn.connection.wait_for_event()?); + } + } + + /// Create a new IME context for a window. + pub(super) fn create_context( + &self, + window: Window, + with_preedit: bool, + spot: Option, + ) -> Result { + let mut client_data = lock!(self.inner_data); + let mut client = lock!(self.client); + + if client_data.disconnected { + return Ok(false); + } + + // Get the current style. + let style = match (client_data.styles, with_preedit) { + (None, _) => return Err(ClientError::Other("No input styles".into())), + (Some((preedit_style, _)), true) => preedit_style, + (Some((_, none_style)), false) => none_style, + }; + + // Setup IC attributes. + let ic_attributes = { + let mut ic_attributes = client + .build_ic_attributes() + .push(AttributeName::ClientWindow, window); + + let ic_style = match style { + Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING, + Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING, + Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE, + }; + + if let Some(spot) = spot.clone() { + ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot); + } + + ic_attributes + .push(AttributeName::InputStyle, ic_style) + .build() + }; + + // Create the IC. + client.create_ic(client_data.input_method.unwrap(), ic_attributes)?; + + // Assign this window to the next IC. + client_data.pending_windows.push_back(PendingData { + window, + style, + spot: spot.unwrap_or(Point { x: 0, y: 0 }), + }); + + Ok(true) + } + + /// Remove an IME context for a window. + pub(super) fn remove_context(&self, window: Window) -> Result { + let mut client_data = lock!(self.inner_data); + + if client_data.disconnected { + return Ok(false); + } + + // Remove the pending window if it's still pending. + let mut removed = false; + client_data.pending_windows.retain(|pending| { + if pending.window == window { + removed = true; + false + } else { + true + } + }); + + if removed { + return Ok(true); + } + + // Remove the IC if it's already created. + if let Some(ic) = client_data.window_data.remove(&window) { + client_data.ic_data.remove(&ic); + + // Destroy the IC. + let im = client_data.input_method.unwrap(); + drop(client_data); + lock!(self.client).destroy_ic(im, ic)?; + } + + Ok(false) + } + + /// Focus an IME context. + pub(super) fn focus_window( + &self, + conn: &XConnection, + window: Window, + ) -> Result { + let client_data = lock!(self.inner_data); + + if client_data.disconnected { + return Ok(false); + } + + let (im, client_data) = self.wait_for_method(conn, client_data)?; + let (ic, _) = self.wait_for_context_for(conn, client_data, window)?; + + if let Some(ic) = ic { + lock!(self.client).set_focus(im, ic)?; + return Ok(true); + } + + Ok(false) + } + + /// Unfocus an IME context. + pub(super) fn unfocus_window( + &self, + conn: &XConnection, + window: Window, + ) -> Result { + let client_data = lock!(self.inner_data); + + if client_data.disconnected { + return Ok(false); + } + + let (im, client_data) = self.wait_for_method(conn, client_data)?; + let (ic, _) = self.wait_for_context_for(conn, client_data, window)?; + + if let Some(ic) = ic { + lock!(self.client).unset_focus(im, ic)?; + return Ok(true); + } + + Ok(false) + } + + /// Set the spot for an IME context. + pub(super) fn set_spot( + &self, + conn: &XConnection, + window: Window, + x: i16, + y: i16, + ) -> Result<(), ClientError> { + let client_data = lock!(self.inner_data); + + if client_data.disconnected { + return Ok(()); + } + + let (im, client_data) = self.wait_for_method(conn, client_data)?; + let (ic, mut client_data) = self.wait_for_context_for(conn, client_data, window)?; + + if let Some(ic) = ic { + // If the IC is not available, or if the spot is the same, then we don't need to update. + let ic_data = match client_data.ic_data.get_mut(&ic) { + Some(ic_data) => ic_data, + None => return Ok(()), + }; + + let new_point = Point { x, y }; + if !ic_data.available() || ic_data.spot == new_point { + return Ok(()); + } + + let mut client = lock!(self.client); + let new_attrs = client + .build_ic_attributes() + .push(AttributeName::SpotLocation, new_point.clone()) + .build(); + client.set_ic_values(im, ic, new_attrs)?; + + // Indicate that we have a new spot. + debug_assert!(ic_data.new_spot.is_none()); + ic_data.new_spot = Some(new_point); + } + + Ok(()) + } + + pub(super) fn set_ime_allowed( + &self, + conn: &XConnection, + window: Window, + allowed: bool, + ) -> Result<(), ClientError> { + let client_data = lock!(self.inner_data); + + if client_data.disconnected { + return Ok(()); + } + + // Get the client info. + let (_, client_data) = self.wait_for_method(conn, client_data)?; + let (ic, client_data) = self.wait_for_context_for(conn, client_data, window)?; + + if let Some(ic) = ic { + let mut spot = None; + + // See if we need to update the allowed state. + if let Some(ic_data) = client_data.ic_data.get(&ic) { + spot = Some(ic_data.spot.clone()); + if ic_data.available() == allowed { + return Ok(()); + } + } + + // Delete and re-install the IC. + drop(client_data); + self.remove_context(window)?; + self.create_context(window, allowed, spot)?; + } + + Ok(()) + } + + /// Wait for this display to have an IM method. + fn wait_for_method<'a>( + &'a self, + xconn: &XConnection, + mut client_data: RefMut<'a, ClientData>, + ) -> Result<(u16, RefMut<'a, ClientData>), ClientError> { + // See if we already have an input method. + if let Some(im) = client_data.input_method { + return Ok((im, client_data)); + } + + // Wait for a new IME event. + loop { + drop(client_data); + + self.block_for_ime(xconn)?; + + // See if we have an input method now. + client_data = lock!(self.inner_data); + if let Some(im) = client_data.input_method { + return Ok((im, client_data)); + } + } + } + + /// Wait for this window to have an IME context. + /// + /// Returns `None` if the window is not registered for IME. + fn wait_for_context_for<'a>( + &'a self, + xconn: &XConnection, + mut client_data: RefMut<'a, ClientData>, + target_window: Window, + ) -> Result<(Option, RefMut<'a, ClientData>), ClientError> { + if let Some(cid) = client_data.window_data.get(&target_window) { + // We already have a context for this window. + return Ok((Some(*cid), client_data)); + } + + // See if the window is in the pending queue. + if !client_data + .pending_windows + .iter() + .any(|PendingData { window, .. }| *window == target_window) + { + // We don't have a context for this window, and it's not in the pending queue. + return Ok((None, client_data)); + } + + loop { + // Wait for a new IME event. + drop(client_data); + + self.block_for_ime(xconn)?; + + client_data = lock!(self.inner_data); + + // See if we have a context for this window now. + if let Some(cid) = client_data.window_data.get(&target_window) { + return Ok((Some(*cid), client_data)); + } + } + } +} + +impl ClientHandler for &ImeData { + fn handle_connect(&mut self, client: &mut XimClient) -> Result<(), ClientError> { + // We are now connected. Request an input method with our current locale. + lock!(self.inner_data).disconnected = false; + client.open(&locale()) + } + + fn handle_open( + &mut self, + client: &mut XimClient, + input_method_id: u16, + ) -> Result<(), ClientError> { + // Store the client's input method ID. + let mut client_data = lock!(self.inner_data); + debug_assert!(client_data.input_method.is_none()); + client_data.input_method = Some(input_method_id); + + // Ask for the IM's attributes. + client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle]) + } + + fn handle_get_im_values( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + mut attributes: xim::AHashMap>, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + let mut preedit_style = None; + let mut none_style = None; + + // Get the input styles. + let styles = { + let style = attributes + .remove(&AttributeName::QueryInputStyle) + .expect("No query input style"); + let mut result = vec![0u32; style.len() / 4]; + + bytemuck::cast_slice_mut::(&mut result).copy_from_slice(&style); + + result + }; + + { + // The styles that we're looking for. + let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING; + let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING; + let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE; + + for style in styles { + let style = InputStyle::from_bits_truncate(style); + + if style == lu_preedit_style { + preedit_style = Some(Style::Preedit); + } else if style == lu_nothing_style { + preedit_style = Some(Style::Nothing); + } else if style == lu_none_style { + none_style = Some(Style::None); + } + } + } + + let (preedit_style, none_style) = match (preedit_style, none_style) { + (None, None) => { + log::error!("No supported input styles found"); + return Ok(()); + } + + (Some(style), None) | (None, Some(style)) => (style, style), + + (Some(preedit_style), Some(none_style)) => (preedit_style, none_style), + }; + + client_data.styles = Some((preedit_style, none_style)); + + Ok(()) + } + + fn handle_close( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + ) -> Result<(), ClientError> { + // We're disconnected. + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + client_data.input_method = None; + + Ok(()) + } + + fn handle_disconnect(&mut self) { + // Indicate that we are now disconnected. + let mut client_data = lock!(self.inner_data); + client_data.disconnected = true; + } + + fn handle_create_ic( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + // Assign the input context's values. + let PendingData { + window, + style, + spot, + } = client_data + .pending_windows + .pop_front() + .expect("No pending windows"); + let ic_data = IcData { + window, + style, + spot, + new_spot: None, + id: input_context_id, + text: Vec::new(), + cursor: 0, + }; + + // Store the input context. + client_data.window_data.insert(ic_data.window, ic_data.id); + client_data.ic_data.insert(input_context_id, ic_data); + + // Indicate our status. + let event = if matches!(style, Style::None) { + ImeEvent::Disabled + } else { + ImeEvent::Enabled + }; + + client_data.events.push_back((window, event)); + + Ok(()) + } + + fn handle_set_ic_values( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + // The input context has had its spot updated. + let ic_data = client_data + .ic_data + .get_mut(&input_context_id) + .expect("No input context data"); + + if let Some(spot) = ic_data.new_spot.take() { + ic_data.spot = spot; + } + + Ok(()) + } + + // IME Callbacks + + fn handle_preedit_start( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + // Get the client data. + if let Some(ic_data) = client_data.ic_data.get_mut(&input_context_id) { + // We're starting a preedit. + ic_data.text.clear(); + ic_data.cursor = 0; + + // Send a message to the window. + let window = ic_data.window; + client_data.events.push_back((window, ImeEvent::Start)); + } + + Ok(()) + } + + fn handle_preedit_done( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + // Get the client data. + if let Some(ic_data) = client_data.ic_data.get_mut(&input_context_id) { + // We're done with a preedit. + ic_data.text.clear(); + ic_data.cursor = 0; + + // Send a message to the window. + let window = ic_data.window; + client_data.events.push_back((window, ImeEvent::End)); + } + + Ok(()) + } + + fn handle_preedit_draw( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + caret: i32, + chg_first: i32, + chg_len: i32, + _status: xim::PreeditDrawStatus, + preedit_string: &str, + _feedbacks: Vec, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + // Get the client data. + if let Some(ic_data) = client_data.ic_data.get_mut(&input_context_id) { + // Set the cursor. + ic_data.cursor = caret as usize; + + // Figure out the range of text to change. + let change_range = chg_first as usize..(chg_first + chg_len) as usize; + + // If the range doesn't fit our current text, warn and return. + if change_range.start > ic_data.text.len() || change_range.end > ic_data.text.len() { + warn!( + "Preedit draw range {}..{} doesn't fit text of length {}", + change_range.start, + change_range.end, + ic_data.text.len() + ); + return Ok(()); + } + + // Update the text in the changed range. + { + let text = &mut ic_data.text; + let mut old_text_tail = text.split_off(change_range.end); + + text.truncate(change_range.start); + text.extend(preedit_string.chars()); + text.append(&mut old_text_tail); + } + + // Send the event. + let cursor_byte_pos = calc_byte_position(&ic_data.text, ic_data.cursor); + let event = ImeEvent::Update(ic_data.text.iter().collect(), cursor_byte_pos); + let window = ic_data.window; + + client_data.events.push_back((window, event)); + } + + Ok(()) + } + + fn handle_preedit_caret( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + input_context_id: u16, + position: &mut i32, + direction: xim::CaretDirection, + _style: xim::CaretStyle, + ) -> Result<(), ClientError> { + if matches!(direction, xim::CaretDirection::AbsolutePosition) { + let mut client_data = lock!(self.inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + if let Some(ic_data) = client_data.ic_data.get_mut(&input_context_id) { + ic_data.cursor = *position as usize; + + // Send the event. + let window = ic_data.window; + let event = ImeEvent::Update(ic_data.text.iter().collect(), *position as usize); + + client_data.events.push_back((window, event)); + } + } + + Ok(()) + } + + // Callbacks we don't care about. + + fn handle_commit( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _text: &str, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_destroy_ic( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_forward_event( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _flag: xim::ForwardEventFlag, + _xev: ::XEvent, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_query_extension( + &mut self, + _client: &mut XimClient, + _extensions: &[xim::Extension], + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_set_event_mask( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _forward_event_mask: u32, + _synchronous_event_mask: u32, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } +} + +fn calc_byte_position(text: &[char], pos: usize) -> usize { + text.iter().take(pos).map(|c| c.len_utf8()).sum() +} diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 419984122ab..4b0c6879e1a 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -5,6 +5,7 @@ mod dnd; mod event_processor; mod events; pub mod ffi; +mod ime; mod monitor; pub mod util; mod window; @@ -20,11 +21,11 @@ pub use self::xdisplay::{XError, XNotSupported}; use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, fmt, ops::Deref, rc::Rc, - sync::mpsc::{Receiver, Sender, TryRecvError}, + sync::mpsc::{self, Receiver, Sender, TryRecvError}, sync::{Arc, Weak}, time::{Duration, Instant}, }; @@ -173,9 +174,10 @@ impl PeekableReceiver { pub struct EventLoopWindowTarget { xconn: Arc, - //ime_sender: ImeSender, root: xproto::Window, - //ime: RefCell, + ime: Option, + ime_sender: Sender, + ime_receiver: Receiver, windows: RefCell>>, redraw_sender: WakeSender, device_event_filter: Cell, @@ -212,6 +214,7 @@ impl EventLoop { let dnd = Dnd::new(Arc::clone(&xconn)); + let (ime_sender, ime_receiver) = mpsc::channel(); /* let (ime_sender, ime_receiver) = mpsc::channel(); let (ime_event_sender, ime_event_receiver) = mpsc::channel(); @@ -280,6 +283,9 @@ impl EventLoop { let window_target = EventLoopWindowTarget { root, windows: Default::default(), + ime: ime::ImeData::new(&xconn, xconn.default_screen).ok(), + ime_sender, + ime_receiver, _marker: ::std::marker::PhantomData, xconn, redraw_sender: WakeSender { @@ -309,7 +315,6 @@ impl EventLoop { first_touch: None, active_window: None, is_composing: false, - event_queue: VecDeque::new(), }; // Register for device hotplug events diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index bd8fe1c3052..ce99b02133b 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -4,7 +4,7 @@ use std::{ os::raw::*, path::Path, ptr, slice, - sync::{Arc, Mutex, MutexGuard}, + sync::{mpsc::Sender, Arc, Mutex, MutexGuard}, }; use libc; @@ -35,6 +35,7 @@ use crate::{ use super::{ atoms::*, ffi, + ime::ImeRequest, util::{self, PlErrorExt, VoidResultExt, XcbVoidCookie}, EventLoopWindowTarget, PlatformError, WakeSender, WindowId, XConnection, }; @@ -123,6 +124,9 @@ pub(crate) struct UnownedWindow { cursor_visible: Mutex, pub shared_state: Mutex, redraw_sender: WakeSender, + + /// Send IME update requests to the event loop. + ime_sender: Mutex>, } impl UnownedWindow { @@ -324,6 +328,7 @@ impl UnownedWindow { waker: event_loop.redraw_sender.waker.clone(), sender: event_loop.redraw_sender.sender.clone(), }, + ime_sender: Mutex::new(event_loop.ime_sender.clone()), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -498,23 +503,13 @@ impl UnownedWindow { )) .ignore_error(); - /*{ - let result = event_loop - .ime - .borrow_mut() - .create_context(window.xwindow as _, false); - if let Err(err) = result { - let e = match err { - ImeContextCreationError::XError(err) => { - OsError::XError(PlatformError::from(err).into()) - } - ImeContextCreationError::Null => { - OsError::XMisc("IME Context creation failed") - } - }; - return Err(os_error!(e)); + { + if let Some(ime) = event_loop.ime.as_ref() { + if ime.create_context(window.xwindow, false, None).is_err() { + return Err(os_error!(OsError::XMisc("IME Context creation failed"))); + } } - }*/ + } // These properties must be set after mapping if window_attrs.maximized { @@ -1591,27 +1586,22 @@ impl UnownedWindow { #[inline] pub fn set_ime_position(&self, spot: Position) { - let _ = spot; - /* let (x, y) = spot.to_physical::(self.scale_factor()).into(); + let _ = self .ime_sender .lock() .unwrap() .send(ImeRequest::Position(self.xwindow as _, x, y)); - */ } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - let _ = allowed; - /* let _ = self .ime_sender .lock() .unwrap() .send(ImeRequest::Allow(self.xwindow as _, allowed)); - */ } #[inline] diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index bf840f0c07a..3fc760da91a 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, error::Error, fmt, mem::ManuallyDrop, @@ -8,9 +8,10 @@ use std::{ sync::Mutex, }; +use x11rb::connection::{Connection, RequestConnection}; +use x11rb::protocol::{xproto, Event}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; -use x11rb::{connection::Connection, protocol::xproto}; use crate::window::CursorIcon; @@ -57,6 +58,9 @@ pub(crate) struct XConnection { /// The name of the window manager. pub wm_name: Mutex>, + + /// The queue of events that we've received from the X server but haven't acted on yet. + pub(super) event_queue: Mutex>, } unsafe impl Send for XConnection {} @@ -108,6 +112,18 @@ impl XConnection { .expect("Failed to create x11rb connection") }; + // Prefetch the extensions that we'll use. + macro_rules! prefetch { + ($($ext:ident),*) => { + $( + connection.prefetch_extension_information(x11rb::protocol::$ext::X11_EXTENSION_NAME) + .expect(concat!("Failed to prefetch ", stringify!($ext))); + )* + } + } + + prefetch!(randr, xinput); + // Begin loading the atoms. let atom_cookie = Atoms::request(&connection).expect("Failed to load atoms"); @@ -131,6 +147,7 @@ impl XConnection { cursor_cache: Default::default(), supported_hints: Mutex::new(Vec::new()), wm_name: Mutex::new(None), + event_queue: Mutex::new(VecDeque::new()), }) }