mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
6 Commits
v0.30.4
...
notgull/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5240c4650 | ||
|
|
1c65f70d59 | ||
|
|
4a30d0eadd | ||
|
|
39b5437951 | ||
|
|
9477514ad5 | ||
|
|
5c9f1b2e9a |
@@ -43,7 +43,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
|
||||
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", "xim"]
|
||||
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
|
||||
wayland-dlopen = ["wayland-backend/dlopen"]
|
||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
|
||||
@@ -167,6 +167,7 @@ wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = tr
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
|
||||
xim = { version = "0.3.0", features = ["x11rb-client", "client"], optional = true }
|
||||
xkbcommon-dl = "0.4.0"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
@@ -231,3 +232,6 @@ web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
|
||||
members = [
|
||||
"run-wasm",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
xim = { git = "https://github.com/forkgull/xim-rs", branch = "x11rb-13" }
|
||||
|
||||
@@ -40,7 +40,6 @@ atom_manager! {
|
||||
WM_DELETE_WINDOW,
|
||||
WM_PROTOCOLS,
|
||||
WM_STATE,
|
||||
XIM_SERVERS,
|
||||
|
||||
// Assorted ICCCM Atoms
|
||||
_NET_WM_ICON,
|
||||
|
||||
@@ -44,7 +44,7 @@ impl From<io::Error> for DndDataParseError {
|
||||
pub(crate) struct Dnd {
|
||||
xconn: Arc<XConnection>,
|
||||
// Populated by XdndEnter event handler
|
||||
pub version: Option<c_long>,
|
||||
pub version: Option<u32>,
|
||||
pub type_list: Option<Vec<xproto::Atom>>,
|
||||
// Populated by XdndPosition event handler
|
||||
pub source_window: Option<xproto::Window>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
862
src/platform_impl/linux/x11/ime.rs
Normal file
862
src/platform_impl/linux/x11/ime.rs
Normal file
@@ -0,0 +1,862 @@
|
||||
//! IME handler, using the xim-rs crate.
|
||||
|
||||
use super::{X11Error, X11rbConnection, XConnection};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::Window;
|
||||
use x11rb::protocol::Event;
|
||||
|
||||
use xim::x11rb::{HasConnection, X11rbClient};
|
||||
use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl HasConnection for XConnection {
|
||||
type Connection = X11rbConnection;
|
||||
|
||||
fn conn(&self) -> &Self::Connection {
|
||||
self.xcb_connection()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, Option<usize>),
|
||||
Commit(String),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Invalid states that an IME client can enter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InvalidImeState {
|
||||
/// The IME has no style information.
|
||||
NoStyle,
|
||||
|
||||
/// No windows in the pending window queue.
|
||||
NoWindows,
|
||||
|
||||
/// Invalid input context.
|
||||
InvalidIc(u16),
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidImeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidImeState::NoStyle => write!(f, "IME has no style information"),
|
||||
InvalidImeState::NoWindows => {
|
||||
write!(f, "IME has no windows in the pending window queue")
|
||||
}
|
||||
InvalidImeState::InvalidIc(ic) => write!(f, "IME has invalid input context {}", ic),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// The IME data for winit.
|
||||
pub(super) struct ImeData {
|
||||
/// The XIM client manager.
|
||||
client: X11rbClient<Arc<XConnection>>,
|
||||
|
||||
/// Relevant IME data.
|
||||
handler: ImeHandler,
|
||||
}
|
||||
|
||||
/// Inner IME handler.
|
||||
struct ImeHandler {
|
||||
/// Handle to the event queue.
|
||||
event_queue: Rc<RefCell<VecDeque<Event>>>,
|
||||
|
||||
/// Whether IME is currently disconnected.
|
||||
disconnected: bool,
|
||||
|
||||
/// IME events waiting to be read.
|
||||
ime_events: VecDeque<(Window, ImeEvent)>,
|
||||
|
||||
/// Windows waiting to be assigned an input context.
|
||||
pending_windows: VecDeque<WindowData>,
|
||||
|
||||
/// Currently registered input styles.
|
||||
styles: Option<(Style, Style)>,
|
||||
|
||||
/// The input method for the display, if there is one.
|
||||
input_method: Option<u16>,
|
||||
|
||||
/// Hash map between input contexts and their associated data.
|
||||
input_contexts: HashMap<u16, IcData>,
|
||||
|
||||
/// Map between window IDs and their associated input contexts.
|
||||
window_contexts: HashMap<Window, u16>,
|
||||
}
|
||||
|
||||
/// Data relevant for each input context.
|
||||
struct IcData {
|
||||
/// Data associated with the window.
|
||||
window: WindowData,
|
||||
|
||||
/// Newly set point for the context.
|
||||
new_spot: Option<Point>,
|
||||
|
||||
/// The current preedit string.
|
||||
///
|
||||
/// We use a `Vec<char>` here instead of a string because the IME indices operate on chars,
|
||||
/// not bytes.
|
||||
text: Vec<char>,
|
||||
|
||||
/// The current cursor position in the preedit string.
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
/// Windows waiting for IME events.
|
||||
struct WindowData {
|
||||
/// The window ID.
|
||||
id: Window,
|
||||
|
||||
/// The style of the window.
|
||||
style: Style,
|
||||
|
||||
/// Current "spot" for the context.
|
||||
spot: Point,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Style {
|
||||
Preedit,
|
||||
Nothing,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ImeData {
|
||||
/// Creates the IME data for the display.
|
||||
pub(super) fn new(
|
||||
conn: &Arc<XConnection>,
|
||||
screen: usize,
|
||||
event_queue: &Rc<RefCell<VecDeque<Event>>>,
|
||||
) -> Result<Self, X11Error> {
|
||||
// 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 mut last_error = X11Error::Ime(ClientError::NoXimServer);
|
||||
|
||||
for im in input_methods {
|
||||
// Try to initialize a client here.
|
||||
match X11rbClient::init(conn.clone(), screen, im) {
|
||||
Ok(client) => {
|
||||
return Ok(Self {
|
||||
client,
|
||||
handler: ImeHandler {
|
||||
event_queue: event_queue.clone(),
|
||||
disconnected: true,
|
||||
ime_events: VecDeque::new(),
|
||||
pending_windows: VecDeque::new(),
|
||||
styles: None,
|
||||
input_method: None,
|
||||
input_contexts: HashMap::new(),
|
||||
window_contexts: HashMap::new(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
log::warn!("Failed to create XIM client for {:?}: {err}", ImData(im));
|
||||
last_error = X11Error::Ime(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error)
|
||||
}
|
||||
|
||||
/// Filter an event.
|
||||
pub(super) fn filter_event(&mut self, event: &Event) -> Result<bool, X11Error> {
|
||||
self.client
|
||||
.filter_event(event, &mut self.handler)
|
||||
.map_err(X11Error::Ime)
|
||||
}
|
||||
|
||||
/// Connection to the X server.
|
||||
fn conn(&self) -> &X11rbConnection {
|
||||
self.client.conn()
|
||||
}
|
||||
|
||||
/// Get an IME event.
|
||||
pub(super) fn next_ime_event(&mut self) -> Option<(Window, ImeEvent)> {
|
||||
self.handler.ime_events.pop_front()
|
||||
}
|
||||
|
||||
/// Create a new IME context for the provided window.
|
||||
pub(super) fn create_context(
|
||||
&mut self,
|
||||
window: Window,
|
||||
with_preedit: bool,
|
||||
spot: Option<Point>,
|
||||
) -> Result<bool, X11Error> {
|
||||
// If we aren't connected, nothing can be done.
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Get the current style.
|
||||
let style = match (self.handler.styles, with_preedit) {
|
||||
(None, _) => return Err(X11Error::InvalidImeState(InvalidImeState::NoStyle)),
|
||||
(Some((preedit_style, _)), true) => preedit_style,
|
||||
(Some((_, none_style)), false) => none_style,
|
||||
};
|
||||
|
||||
// Setup IC attributes.
|
||||
let ic_attributes = {
|
||||
let mut ic_attributes = self
|
||||
.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.
|
||||
self.client.create_ic(method, ic_attributes)?;
|
||||
|
||||
// Add to the waiting window list.
|
||||
self.handler.pending_windows.push_back(WindowData {
|
||||
id: 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(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
let method = match self.handler.input_method {
|
||||
Some(im) => im,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// Remove the pending window if it's still pending.
|
||||
let mut removed = false;
|
||||
self.handler.pending_windows.retain(|pending| {
|
||||
if pending.id == window {
|
||||
removed = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if removed {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Remove the IC if it's already created.
|
||||
if let Some(ic) = self.handler.window_contexts.remove(&window) {
|
||||
self.handler.input_contexts.remove(&ic);
|
||||
|
||||
// Destroy the IC.
|
||||
self.client.destroy_ic(method, ic)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Focus an IME context.
|
||||
pub(super) fn focus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.set_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Unfocus an IME context.
|
||||
pub(super) fn unfocus_window(&mut self, window: Window) -> Result<bool, X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
self.client.unset_focus(method, ic)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Set the spot for an IME context.
|
||||
pub(super) fn set_spot(&mut self, window: Window, x: i16, y: i16) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let method = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(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 self.handler.input_contexts.get_mut(&ic) {
|
||||
Some(ic_data) => ic_data,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let new_point = Point { x, y };
|
||||
if !matches!(ic_data.window.style, Style::None) || ic_data.window.spot == new_point {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_attrs = self
|
||||
.client
|
||||
.build_ic_attributes()
|
||||
.push(AttributeName::SpotLocation, new_point.clone())
|
||||
.build();
|
||||
self.client.set_ic_values(method, 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(
|
||||
&mut self,
|
||||
window: Window,
|
||||
allowed: bool,
|
||||
) -> Result<(), X11Error> {
|
||||
if self.handler.disconnected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the client info.
|
||||
let _ = self.wait_for_method()?;
|
||||
let ic = self.wait_for_context(window)?;
|
||||
|
||||
if let Some(ic) = ic {
|
||||
let mut spot = None;
|
||||
|
||||
// See if we need to update the allowed state.
|
||||
if let Some(ic_data) = self.handler.input_contexts.get(&ic) {
|
||||
spot = Some(ic_data.window.spot.clone());
|
||||
if matches!(ic_data.window.style, Style::None) != allowed {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete and re-install the IC.
|
||||
self.remove_context(window)?;
|
||||
self.create_context(window, allowed, spot)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the input method to be set.
|
||||
fn wait_for_method(&mut self) -> Result<u16, X11Error> {
|
||||
loop {
|
||||
if let Some(im) = self.handler.input_method {
|
||||
return Ok(im);
|
||||
}
|
||||
|
||||
// Wait and hope the input method is set.
|
||||
self.block_for_ime()?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for an input context to be set.
|
||||
fn wait_for_context(&mut self, window: Window) -> Result<Option<u16>, X11Error> {
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
|
||||
// If the window isn't in our pending windows queue, there's no way for it to get an IC.
|
||||
if !self
|
||||
.handler
|
||||
.pending_windows
|
||||
.iter()
|
||||
.any(|WindowData { id, .. }| *id == window)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.block_for_ime()?;
|
||||
if let Some(cid) = self.handler.window_contexts.get(&window) {
|
||||
return Ok(Some(*cid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until we've acted on an IME event.
|
||||
fn block_for_ime(&mut self) -> Result<(), X11Error> {
|
||||
let mut last_event = self.conn().poll_for_event()?;
|
||||
|
||||
loop {
|
||||
if let Some(last_event) = last_event.as_ref() {
|
||||
if self.filter_event(last_event)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// This scope keeps track of the event queue handle.
|
||||
{
|
||||
// Check the event queue for events.
|
||||
let event_queue = self.handler.event_queue.clone();
|
||||
let mut event_queue = event_queue.borrow_mut();
|
||||
|
||||
// Check the event queue for events we can use.
|
||||
let mut found_event = false;
|
||||
let mut last_err = None;
|
||||
event_queue.retain(|event| match self.filter_event(event) {
|
||||
Ok(false) => {
|
||||
found_event = true;
|
||||
true
|
||||
}
|
||||
Ok(true) => false,
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
// Push our own event to the queue.
|
||||
if let Some(last_event) = last_event.take() {
|
||||
event_queue.push_back(last_event);
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
if let Some(err) = last_err {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// If we found an event, then we're done.
|
||||
if found_event {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Waiting for IME event");
|
||||
last_event = Some(self.conn().wait_for_event()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: xim::Client> ClientHandler<C> for ImeHandler {
|
||||
fn handle_connect(&mut self, client: &mut C) -> Result<(), ClientError> {
|
||||
// We have been connected, now request a new input method for our current locale.
|
||||
self.disconnected = false;
|
||||
client.open(&locale())
|
||||
}
|
||||
|
||||
fn handle_disconnect(&mut self) {
|
||||
// We are now disconnected.
|
||||
self.disconnected = true;
|
||||
}
|
||||
|
||||
fn handle_open(&mut self, client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// We now have an input method.
|
||||
debug_assert!(self.input_method.is_none());
|
||||
self.input_method = Some(input_method_id);
|
||||
|
||||
// Ask for the IM's attributes.
|
||||
client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle])
|
||||
}
|
||||
|
||||
fn handle_close(&mut self, _client: &mut C, input_method_id: u16) -> Result<(), ClientError> {
|
||||
// No more input method.
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
self.input_method = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_im_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
mut attributes: xim::AHashMap<xim::AttributeName, Vec<u8>>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the input styles.
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
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::<u32, u8>(&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),
|
||||
};
|
||||
|
||||
self.styles = Some((preedit_style, none_style));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_create_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the window that wanted the IC context.
|
||||
let window = self
|
||||
.pending_windows
|
||||
.pop_front()
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::NoWindows))?;
|
||||
|
||||
// Create the IC data.
|
||||
let ic_data = IcData {
|
||||
window,
|
||||
new_spot: None,
|
||||
text: Vec::new(),
|
||||
cursor: 0,
|
||||
};
|
||||
|
||||
// Store the context.
|
||||
let (window, style) = (ic_data.window.id, ic_data.window.style);
|
||||
self.input_contexts.insert(input_context_id, ic_data);
|
||||
self.window_contexts.insert(window, input_context_id);
|
||||
|
||||
// Indicate our status.
|
||||
let event = if matches!(style, Style::Nothing) {
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
self.ime_events.push_back((window, event));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_destroy_ic(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
// This is already handled by the higher-level function.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_ic_values(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the IC data.
|
||||
let ic_data = self
|
||||
.input_contexts
|
||||
.get_mut(&input_context_id)
|
||||
.ok_or_else(|| invalid_state(InvalidImeState::InvalidIc(input_context_id)))?;
|
||||
|
||||
// Move up the new spot
|
||||
if let Some(spot) = ic_data.new_spot.take() {
|
||||
ic_data.window.spot = spot;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_start(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Start a pre-edit.
|
||||
ic_data.text.clear();
|
||||
ic_data.cursor = 0;
|
||||
|
||||
// Indicate the start.
|
||||
self.ime_events
|
||||
.push_back((ic_data.window.id, ImeEvent::Start));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_draw(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
caret: i32,
|
||||
chg_first: i32,
|
||||
chg_len: i32,
|
||||
_status: xim::PreeditDrawStatus,
|
||||
preedit_string: &str,
|
||||
_feedbacks: Vec<xim::Feedback>,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.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(), Some(cursor_byte_pos));
|
||||
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_caret(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
position: &mut i32,
|
||||
direction: xim::CaretDirection,
|
||||
_style: xim::CaretStyle,
|
||||
) -> Result<(), ClientError> {
|
||||
// We only care about absolute position.
|
||||
if matches!(direction, xim::CaretDirection::AbsolutePosition) {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
ic_data.cursor = *position as usize;
|
||||
|
||||
// Send the event
|
||||
let event =
|
||||
ImeEvent::Update(ic_data.text.iter().collect(), Some(*position as usize));
|
||||
self.ime_events.push_back((ic_data.window.id, event));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preedit_done(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.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.id;
|
||||
self.ime_events.push_back((window, ImeEvent::End));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_commit(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
input_method_id: u16,
|
||||
input_context_id: u16,
|
||||
text: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
debug_assert_eq!(self.input_method, Some(input_method_id));
|
||||
|
||||
// Get the client data.
|
||||
if let Some(ic_data) = self.input_contexts.get_mut(&input_context_id) {
|
||||
// Send a message to the window.
|
||||
let window = ic_data.window.id;
|
||||
self.ime_events
|
||||
.push_back((window, ImeEvent::Commit(text.to_owned())));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_query_extension(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_extensions: &[xim::Extension],
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_forward_event(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_flag: xim::ForwardEventFlag,
|
||||
_xev: C::XEvent,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_event_mask(
|
||||
&mut self,
|
||||
_client: &mut C,
|
||||
_input_method_id: u16,
|
||||
_input_context_id: u16,
|
||||
_forward_event_mask: u32,
|
||||
_synchronous_event_mask: u32,
|
||||
) -> Result<(), ClientError> {
|
||||
// Don't care.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn invalid_state(state: InvalidImeState) -> ClientError {
|
||||
ClientError::Other(Box::new(X11Error::InvalidImeState(state)))
|
||||
}
|
||||
|
||||
struct ImData(Option<&'static str>);
|
||||
|
||||
impl fmt::Debug for ImData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
Some(name) => write!(f, "\"{}\"", name),
|
||||
None => write!(f, "default input method"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current locale.
|
||||
fn locale() -> String {
|
||||
use std::ffi::CStr;
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter().take(pos).map(|c| c.len_utf8()).sum()
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::{ImeContext, ImeContextCreationError},
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::PotentialInputMethods,
|
||||
};
|
||||
|
||||
pub(crate) unsafe fn xim_set_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
xim: ffi::XIM,
|
||||
field: *const c_char,
|
||||
callback: *mut ffi::XIMCallback,
|
||||
) -> Result<(), XError> {
|
||||
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
|
||||
// access that isn't type-checked.
|
||||
unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
// Set a callback for when an input method matching the current locale modifiers becomes
|
||||
// available. Note that this has nothing to do with what input methods are open or able to be
|
||||
// opened, and simply uses the modifiers that are set when the callback is set.
|
||||
// * This is called per locale modifier, not per input method opened with that locale modifier.
|
||||
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
|
||||
// input contexts would always silently fail to use the input method.
|
||||
pub(crate) unsafe fn set_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn unset_instantiate_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(xim_instantiate_callback),
|
||||
client_data,
|
||||
)
|
||||
};
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn set_destroy_callback(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
inner: &ImeInner,
|
||||
) -> Result<(), XError> {
|
||||
unsafe {
|
||||
xim_set_callback(
|
||||
xconn,
|
||||
im,
|
||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
||||
&inner.destroy_callback as *const _ as *mut _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ReplaceImError {
|
||||
// Boxed to prevent large error type
|
||||
MethodOpenFailed(Box<PotentialInputMethods>),
|
||||
ContextCreationFailed(ImeContextCreationError),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
}
|
||||
|
||||
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
|
||||
// includes replacing all existing input contexts and free'ing resources as necessary. This only
|
||||
// modifies existing state if all operations succeed.
|
||||
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
|
||||
let (new_im, is_fallback) = {
|
||||
let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) };
|
||||
let is_fallback = new_im.is_fallback();
|
||||
(
|
||||
new_im.ok().ok_or_else(|| {
|
||||
ReplaceImError::MethodOpenFailed(Box::new(unsafe {
|
||||
(*inner).potential_input_methods.clone()
|
||||
}))
|
||||
})?,
|
||||
is_fallback,
|
||||
)
|
||||
};
|
||||
|
||||
// It's important to always set a destroy callback, since there's otherwise potential for us
|
||||
// to try to use or free a resource that's already been destroyed on the server.
|
||||
{
|
||||
let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) };
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result
|
||||
}
|
||||
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
||||
|
||||
let mut new_contexts = HashMap::new();
|
||||
for (window, old_context) in unsafe { (*inner).contexts.iter() } {
|
||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed = old_context
|
||||
.as_ref()
|
||||
.map(|old_context| old_context.is_allowed())
|
||||
.unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed {
|
||||
new_im.preedit_style
|
||||
} else {
|
||||
new_im.none_style
|
||||
};
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
let _ = unsafe { close_im(xconn, new_im.im) };
|
||||
}
|
||||
result.map_err(ReplaceImError::ContextCreationFailed)?
|
||||
};
|
||||
new_contexts.insert(*window, Some(new_context));
|
||||
}
|
||||
|
||||
// If we've made it this far, everything succeeded.
|
||||
unsafe {
|
||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
||||
let _ = (*inner).close_im_if_necessary();
|
||||
(*inner).im = Some(new_im);
|
||||
(*inner).contexts = new_contexts;
|
||||
(*inner).is_destroyed = false;
|
||||
(*inner).is_fallback = is_fallback;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn xim_instantiate_callback(
|
||||
_display: *mut ffi::Display,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe {
|
||||
let _ = unset_instantiate_callback(xconn, client_data);
|
||||
(*inner).is_fallback = false;
|
||||
},
|
||||
Err(err) => unsafe {
|
||||
if (*inner).is_destroyed {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to reopen input method: {err:?}");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is triggered when the input method is closed on the server end. When this
|
||||
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
|
||||
// free'd (attempting to do so causes our connection to freeze).
|
||||
pub unsafe extern "C" fn xim_destroy_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
// This field is unsupplied.
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let inner: *mut ImeInner = client_data as _;
|
||||
if !inner.is_null() {
|
||||
unsafe { (*inner).is_destroyed = true };
|
||||
let xconn = unsafe { &(*inner).xconn };
|
||||
if unsafe { !(*inner).is_fallback } {
|
||||
let _ = unsafe { set_instantiate_callback(xconn, client_data) };
|
||||
// Attempt to open fallback input method.
|
||||
match unsafe { replace_im(inner) } {
|
||||
Ok(()) => unsafe { (*inner).is_fallback = true },
|
||||
Err(err) => {
|
||||
// We have no usable input methods!
|
||||
panic!("Failed to open fallback input method: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_short;
|
||||
use std::sync::Arc;
|
||||
use std::{mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ImeContextCreationError {
|
||||
/// Got the error from Xlib.
|
||||
XError(XError),
|
||||
|
||||
/// Got null pointer from Xlib but without exact reason.
|
||||
Null,
|
||||
}
|
||||
|
||||
/// The callback used by XIM preedit functions.
|
||||
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);
|
||||
|
||||
/// Wrapper for creating XIM callbacks.
|
||||
#[inline]
|
||||
fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback {
|
||||
XIMCallback {
|
||||
client_data,
|
||||
callback: Some(callback),
|
||||
}
|
||||
}
|
||||
|
||||
/// The server started preedit.
|
||||
extern "C" fn preedit_start_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) -> i32 {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
client_data.text.clear();
|
||||
client_data.cursor_pos = 0;
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::Start))
|
||||
.expect("failed to send preedit start event");
|
||||
-1
|
||||
}
|
||||
|
||||
/// Done callback is used when the preedit should be hidden.
|
||||
extern "C" fn preedit_done_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
_call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
|
||||
// Drop text buffer and reset cursor position on done.
|
||||
client_data.text = Vec::new();
|
||||
client_data.cursor_pos = 0;
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((client_data.window, ImeEvent::End))
|
||||
.expect("failed to send preedit end event");
|
||||
}
|
||||
|
||||
fn calc_byte_position(text: &[char], pos: usize) -> usize {
|
||||
text.iter()
|
||||
.take(pos)
|
||||
.fold(0, |byte_pos, text| byte_pos + text.len_utf8())
|
||||
}
|
||||
|
||||
/// Preedit text information to be drawn inline by the client.
|
||||
extern "C" fn preedit_draw_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) };
|
||||
client_data.cursor_pos = call_data.caret as usize;
|
||||
|
||||
let chg_range =
|
||||
call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize;
|
||||
if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() {
|
||||
warn!(
|
||||
"invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}",
|
||||
client_data.text.len(),
|
||||
call_data.chg_first,
|
||||
call_data.chg_length
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// NULL indicate text deletion
|
||||
let mut new_chars = if call_data.text.is_null() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let xim_text = unsafe { &mut *(call_data.text) };
|
||||
if xim_text.encoding_is_wchar > 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { xim_text.string.multi_byte };
|
||||
|
||||
if new_text.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_text = unsafe { CStr::from_ptr(new_text) };
|
||||
|
||||
String::from(new_text.to_str().expect("Invalid UTF-8 String from IME"))
|
||||
.chars()
|
||||
.collect()
|
||||
};
|
||||
let mut old_text_tail = client_data.text.split_off(chg_range.end);
|
||||
client_data.text.truncate(chg_range.start);
|
||||
client_data.text.append(&mut new_chars);
|
||||
client_data.text.append(&mut old_text_tail);
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
|
||||
/// Handling of cursor movements in preedit text.
|
||||
extern "C" fn preedit_caret_callback(
|
||||
_xim: ffi::XIM,
|
||||
client_data: ffi::XPointer,
|
||||
call_data: ffi::XPointer,
|
||||
) {
|
||||
let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) };
|
||||
let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) };
|
||||
|
||||
if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition {
|
||||
client_data.cursor_pos = call_data.position as usize;
|
||||
let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos);
|
||||
|
||||
client_data
|
||||
.event_sender
|
||||
.send((
|
||||
client_data.window,
|
||||
ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos),
|
||||
))
|
||||
.expect("failed to send preedit update event");
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to simplify callback creation and latter passing into Xlib XIM.
|
||||
struct PreeditCallbacks {
|
||||
start_callback: ffi::XIMCallback,
|
||||
done_callback: ffi::XIMCallback,
|
||||
draw_callback: ffi::XIMCallback,
|
||||
caret_callback: ffi::XIMCallback,
|
||||
}
|
||||
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
let draw_callback = create_xim_callback(client_data, preedit_draw_callback);
|
||||
|
||||
PreeditCallbacks {
|
||||
start_callback,
|
||||
done_callback,
|
||||
caret_callback,
|
||||
draw_callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeContextClientData {
|
||||
window: ffi::Window,
|
||||
event_sender: ImeEventSender,
|
||||
text: Vec<char>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
// XXX: this struct doesn't destroy its XIC resource when dropped.
|
||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
||||
// through `ImeInner`.
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from
|
||||
// there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
event_sender,
|
||||
text: Vec::new(),
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
xconn
|
||||
.check_errors()
|
||||
.map_err(ImeContextCreationError::XError)?;
|
||||
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
// Set the spot location, if it's present.
|
||||
if let Some(ic_spot) = ic_spot {
|
||||
context.set_spot(xconn, ic_spot.x, ic_spot.y)
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
unsafe fn create_none_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_preedit_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
client_data: ffi::XPointer,
|
||||
) -> Option<ffi::XIC> {
|
||||
let preedit_callbacks = PreeditCallbacks::new(client_data);
|
||||
let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe {
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNPreeditStartCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.start_callback) as *const _,
|
||||
ffi::XNPreeditDoneCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.done_callback) as *const _,
|
||||
ffi::XNPreeditCaretCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.caret_callback) as *const _,
|
||||
ffi::XNPreeditDrawCallback_0.as_ptr() as *const _,
|
||||
&(preedit_callbacks.draw_callback) as *const _,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
})
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
unsafe fn create_nothing_ic(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: XIMStyle,
|
||||
window: ffi::Window,
|
||||
) -> Option<ffi::XIC> {
|
||||
let ic = unsafe {
|
||||
(xconn.xlib.XCreateIC)(
|
||||
im,
|
||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
||||
style,
|
||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
||||
window,
|
||||
ptr::null_mut::<()>(),
|
||||
)
|
||||
};
|
||||
|
||||
(!ic.is_null()).then_some(ic)
|
||||
}
|
||||
|
||||
pub(crate) fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XSetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
||||
unsafe {
|
||||
(xconn.xlib.XUnsetICFocus)(self.ic);
|
||||
}
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
// are being used. Certain IMEs do show selection window, but it's placed in bottom left of the
|
||||
// window and couldn't be changed.
|
||||
//
|
||||
// For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580.
|
||||
pub(crate) fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
||||
if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
|
||||
unsafe {
|
||||
let preedit_attr = util::memory::XSmartPointer::new(
|
||||
xconn,
|
||||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
.expect("XVaCreateNestedList returned NULL");
|
||||
|
||||
(xconn.xlib.XSetICValues)(
|
||||
self.ic,
|
||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
||||
preedit_attr.ptr,
|
||||
ptr::null_mut::<()>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
use std::{collections::HashMap, mem, sync::Arc};
|
||||
|
||||
use super::{ffi, XConnection, XError};
|
||||
|
||||
use super::{
|
||||
context::ImeContext,
|
||||
input_method::{InputMethod, PotentialInputMethods},
|
||||
};
|
||||
use crate::platform_impl::platform::x11::ime::ImeEventSender;
|
||||
|
||||
pub(crate) unsafe fn close_im(xconn: &Arc<XConnection>, im: ffi::XIM) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XCloseIM)(im) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy_ic(xconn: &Arc<XConnection>, ic: ffi::XIC) -> Result<(), XError> {
|
||||
unsafe { (xconn.xlib.XDestroyIC)(ic) };
|
||||
xconn.check_errors()
|
||||
}
|
||||
|
||||
pub(crate) struct ImeInner {
|
||||
pub xconn: Arc<XConnection>,
|
||||
pub im: Option<InputMethod>,
|
||||
pub potential_input_methods: PotentialInputMethods,
|
||||
pub contexts: HashMap<ffi::Window, Option<ImeContext>>,
|
||||
// WARNING: this is initially zeroed!
|
||||
pub destroy_callback: ffi::XIMCallback,
|
||||
pub event_sender: ImeEventSender,
|
||||
// Indicates whether or not the the input method was destroyed on the server end
|
||||
// (i.e. if ibus/fcitx/etc. was terminated/restarted)
|
||||
pub is_destroyed: bool,
|
||||
pub is_fallback: bool,
|
||||
}
|
||||
|
||||
impl ImeInner {
|
||||
pub(crate) fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
potential_input_methods: PotentialInputMethods,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Self {
|
||||
ImeInner {
|
||||
xconn,
|
||||
im: None,
|
||||
potential_input_methods,
|
||||
contexts: HashMap::new(),
|
||||
destroy_callback: unsafe { mem::zeroed() },
|
||||
event_sender,
|
||||
is_destroyed: false,
|
||||
is_fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
|
||||
if !self.is_destroyed && self.im.is_some() {
|
||||
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result<bool, XError> {
|
||||
if !self.is_destroyed {
|
||||
unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result<bool, XError> {
|
||||
for context in self.contexts.values().flatten() {
|
||||
unsafe { self.destroy_ic_if_necessary(context.ic)? };
|
||||
}
|
||||
Ok(!self.is_destroyed)
|
||||
}
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::{CStr, CString, IntoStringError},
|
||||
fmt,
|
||||
os::raw::{c_char, c_ulong, c_ushort},
|
||||
ptr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{super::atoms::*, ffi, util, XConnection, XError};
|
||||
use once_cell::sync::Lazy;
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
|
||||
|
||||
unsafe fn open_im(xconn: &Arc<XConnection>, locale_modifiers: &CStr) -> Option<ffi::XIM> {
|
||||
let _lock = GLOBAL_LOCK.lock();
|
||||
|
||||
// XSetLocaleModifiers returns...
|
||||
// * The current locale modifiers if it's given a NULL pointer.
|
||||
// * The new locale modifiers if we succeeded in setting them.
|
||||
// * NULL if the locale modifiers string is malformed or if the
|
||||
// current locale is not supported by Xlib.
|
||||
unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) };
|
||||
|
||||
let im = unsafe {
|
||||
(xconn.xlib.XOpenIM)(
|
||||
xconn.display,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if im.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(im)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputMethod {
|
||||
pub im: ffi::XIM,
|
||||
pub preedit_style: Style,
|
||||
pub none_style: Style,
|
||||
_name: String,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
fn new(xconn: &Arc<XConnection>, im: ffi::XIM, name: String) -> Option<Self> {
|
||||
let mut styles: *mut XIMStyles = std::ptr::null_mut();
|
||||
|
||||
// Query the styles supported by the XIM.
|
||||
unsafe {
|
||||
if !(xconn.xlib.XGetIMValues)(
|
||||
im,
|
||||
ffi::XNQueryInputStyle_0.as_ptr() as *const _,
|
||||
(&mut styles) as *mut _,
|
||||
std::ptr::null_mut::<()>(),
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut preedit_style = None;
|
||||
let mut none_style = None;
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _)
|
||||
.iter()
|
||||
.for_each(|style| match *style {
|
||||
XIM_PREEDIT_STYLE => {
|
||||
preedit_style = Some(Style::Preedit(*style));
|
||||
}
|
||||
XIM_NOTHING_STYLE if preedit_style.is_none() => {
|
||||
preedit_style = Some(Style::Nothing(*style))
|
||||
}
|
||||
XIM_NONE_STYLE => none_style = Some(Style::None(*style)),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
(xconn.xlib.XFree)(styles.cast());
|
||||
};
|
||||
|
||||
if preedit_style.is_none() && none_style.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
|
||||
let none_style = none_style.unwrap_or(preedit_style);
|
||||
|
||||
Some(InputMethod {
|
||||
im,
|
||||
_name: name,
|
||||
preedit_style,
|
||||
none_style,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle;
|
||||
const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle;
|
||||
|
||||
/// Style of the IME context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
/// Preedit callbacks.
|
||||
Preedit(XIMStyle),
|
||||
|
||||
/// Nothing.
|
||||
Nothing(XIMStyle),
|
||||
|
||||
/// No IME.
|
||||
None(XIMStyle),
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style::None(XIM_NONE_STYLE)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct XIMStyles {
|
||||
count_styles: c_ushort,
|
||||
supported_styles: *const XIMStyle,
|
||||
}
|
||||
|
||||
pub(crate) type XIMStyle = c_ulong;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMethodResult {
|
||||
/// Input method used locale modifier from `XMODIFIERS` environment variable.
|
||||
XModifiers(InputMethod),
|
||||
/// Input method used internal fallback locale modifier.
|
||||
Fallback(InputMethod),
|
||||
/// Input method could not be opened using any locale modifier tried.
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl InputMethodResult {
|
||||
pub fn is_fallback(&self) -> bool {
|
||||
matches!(self, InputMethodResult::Fallback(_))
|
||||
}
|
||||
|
||||
pub fn ok(self) -> Option<InputMethod> {
|
||||
use self::InputMethodResult::*;
|
||||
match self {
|
||||
XModifiers(im) | Fallback(im) => Some(im),
|
||||
Failure => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum GetXimServersError {
|
||||
XError(XError),
|
||||
GetPropertyError(util::GetPropertyError),
|
||||
InvalidUtf8(IntoStringError),
|
||||
}
|
||||
|
||||
impl From<util::GetPropertyError> for GetXimServersError {
|
||||
fn from(error: util::GetPropertyError) -> Self {
|
||||
GetXimServersError::GetPropertyError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting
|
||||
// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named
|
||||
// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably
|
||||
// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale
|
||||
// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set
|
||||
// XMODIFIERS to `@server=ibus`?!?"
|
||||
unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXimServersError> {
|
||||
let atoms = xconn.atoms();
|
||||
let servers_atom = atoms[XIM_SERVERS];
|
||||
|
||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
||||
|
||||
let mut atoms: Vec<ffi::Atom> = xconn
|
||||
.get_property::<xproto::Atom>(
|
||||
root as xproto::Window,
|
||||
servers_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::ATOM),
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(ffi::Atom::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
unsafe {
|
||||
(xconn.xlib.XGetAtomNames)(
|
||||
xconn.display,
|
||||
atoms.as_mut_ptr(),
|
||||
atoms.len() as _,
|
||||
names.as_mut_ptr() as _,
|
||||
)
|
||||
};
|
||||
unsafe { names.set_len(atoms.len()) };
|
||||
|
||||
let mut formatted_names = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
let string = unsafe { CStr::from_ptr(name) }
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.map_err(GetXimServersError::InvalidUtf8)?;
|
||||
unsafe { (xconn.xlib.XFree)(name as _) };
|
||||
formatted_names.push(string.replace("@server=", "@im="));
|
||||
}
|
||||
xconn.check_errors().map_err(GetXimServersError::XError)?;
|
||||
Ok(formatted_names)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InputMethodName {
|
||||
c_string: CString,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl InputMethodName {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
let c_string = CString::new(string.clone())
|
||||
.expect("String used to construct CString contained null byte");
|
||||
InputMethodName { c_string, string }
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
let c_string =
|
||||
CString::new(string).expect("String used to construct CString contained null byte");
|
||||
InputMethodName {
|
||||
c_string,
|
||||
string: string.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InputMethodName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.string.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PotentialInputMethod {
|
||||
name: InputMethodName,
|
||||
successful: Option<bool>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethod {
|
||||
pub fn from_string(string: String) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_string(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(string: &str) -> Self {
|
||||
PotentialInputMethod {
|
||||
name: InputMethodName::from_str(string),
|
||||
successful: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.successful = None;
|
||||
}
|
||||
|
||||
pub fn open_im(&mut self, xconn: &Arc<XConnection>) -> Option<InputMethod> {
|
||||
let im = unsafe { open_im(xconn, &self.name.c_string) };
|
||||
self.successful = Some(im.is_some());
|
||||
im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
// By logging this struct, you get a sequential listing of every locale modifier tried, where it
|
||||
// came from, and if it succeeded.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PotentialInputMethods {
|
||||
// On correctly configured systems, the XMODIFIERS environment variable tells us everything we
|
||||
// need to know.
|
||||
xmodifiers: Option<PotentialInputMethod>,
|
||||
// We have some standard options at our disposal that should ostensibly always work. For users
|
||||
// who only need compose sequences, this ensures that the program launches without a hitch
|
||||
// For users who need more sophisticated IME features, this is more or less a silent failure.
|
||||
// Logging features should be added in the future to allow both audiences to be effectively
|
||||
// served.
|
||||
fallbacks: [PotentialInputMethod; 2],
|
||||
// For diagnostic purposes, we include the list of XIM servers that the server reports as
|
||||
// being available.
|
||||
_xim_servers: Result<Vec<String>, GetXimServersError>,
|
||||
}
|
||||
|
||||
impl PotentialInputMethods {
|
||||
pub fn new(xconn: &Arc<XConnection>) -> Self {
|
||||
let xmodifiers = env::var("XMODIFIERS")
|
||||
.ok()
|
||||
.map(PotentialInputMethod::from_string);
|
||||
PotentialInputMethods {
|
||||
// Since passing "" to XSetLocaleModifiers results in it defaulting to the value of
|
||||
// XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply
|
||||
// running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is
|
||||
// defined in the profile (or parent environment) then that parent XMODIFIERS is used.
|
||||
// If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then
|
||||
// XSetLocaleModifiers uses the default local input method. Note that defining
|
||||
// XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in
|
||||
// that case, we get `None` and end up skipping ahead to the next method.
|
||||
xmodifiers,
|
||||
fallbacks: [
|
||||
// This is a standard input method that supports compose sequences, which should
|
||||
// always be available. `@im=none` appears to mean the same thing.
|
||||
PotentialInputMethod::from_str("@im=local"),
|
||||
// This explicitly specifies to use the implementation-dependent default, though
|
||||
// that seems to be equivalent to just using the local input method.
|
||||
PotentialInputMethod::from_str("@im="),
|
||||
],
|
||||
// The XIM_SERVERS property can have surprising values. For instance, when I exited
|
||||
// ibus to run fcitx, it retained the value denoting ibus. Even more surprising is
|
||||
// that the fcitx input method could only be successfully opened using "@im=ibus".
|
||||
// Presumably due to this quirk, it's actually possible to alternate between ibus and
|
||||
// fcitx in a running application.
|
||||
_xim_servers: unsafe { get_xim_servers(xconn) },
|
||||
}
|
||||
}
|
||||
|
||||
// This resets the `successful` field of every potential input method, ensuring we have
|
||||
// accurate information when this struct is re-used by the destruction/instantiation callbacks.
|
||||
fn reset(&mut self) {
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
input_method.reset();
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
input_method.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_im(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
callback: Option<&dyn Fn()>,
|
||||
) -> InputMethodResult {
|
||||
use self::InputMethodResult::*;
|
||||
|
||||
self.reset();
|
||||
|
||||
if let Some(ref mut input_method) = self.xmodifiers {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return XModifiers(im);
|
||||
} else if let Some(ref callback) = callback {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
for input_method in &mut self.fallbacks {
|
||||
let im = input_method.open_im(xconn);
|
||||
if let Some(im) = im {
|
||||
return Fallback(im);
|
||||
}
|
||||
}
|
||||
|
||||
Failure
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
// Important: all XIM calls need to happen from the same thread!
|
||||
|
||||
mod callbacks;
|
||||
mod context;
|
||||
mod inner;
|
||||
mod input_method;
|
||||
|
||||
use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::{
|
||||
callbacks::*,
|
||||
context::ImeContext,
|
||||
inner::{close_im, ImeInner},
|
||||
input_method::{PotentialInputMethods, Style},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum ImeEvent {
|
||||
Enabled,
|
||||
Start,
|
||||
Update(String, usize),
|
||||
End,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub type ImeReceiver = Receiver<ImeRequest>;
|
||||
pub type ImeSender = Sender<ImeRequest>;
|
||||
pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>;
|
||||
pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>;
|
||||
|
||||
/// Request to control XIM handler from the window.
|
||||
pub enum ImeRequest {
|
||||
/// Set IME spot position for given `window_id`.
|
||||
Position(ffi::Window, i16, i16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ImeCreationError {
|
||||
// Boxed to prevent large error type
|
||||
OpenFailure(Box<PotentialInputMethods>),
|
||||
SetDestroyCallbackFailed(XError),
|
||||
}
|
||||
|
||||
pub(crate) struct Ime {
|
||||
xconn: Arc<XConnection>,
|
||||
// The actual meat of this struct is boxed away, since it needs to have a fixed location in
|
||||
// memory so we can pass a pointer to it around.
|
||||
inner: Box<ImeInner>,
|
||||
}
|
||||
|
||||
impl Ime {
|
||||
pub fn new(
|
||||
xconn: Arc<XConnection>,
|
||||
event_sender: ImeEventSender,
|
||||
) -> Result<Self, ImeCreationError> {
|
||||
let potential_input_methods = PotentialInputMethods::new(&xconn);
|
||||
|
||||
let (mut inner, client_data) = {
|
||||
let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender));
|
||||
let inner_ptr = Box::into_raw(inner);
|
||||
let client_data = inner_ptr as _;
|
||||
let destroy_callback = ffi::XIMCallback {
|
||||
client_data,
|
||||
callback: Some(xim_destroy_callback),
|
||||
};
|
||||
inner = unsafe { Box::from_raw(inner_ptr) };
|
||||
inner.destroy_callback = destroy_callback;
|
||||
(inner, client_data)
|
||||
};
|
||||
|
||||
let xconn = Arc::clone(&inner.xconn);
|
||||
|
||||
let input_method = inner.potential_input_methods.open_im(
|
||||
&xconn,
|
||||
Some(&|| {
|
||||
let _ = unsafe { set_instantiate_callback(&xconn, client_data) };
|
||||
}),
|
||||
);
|
||||
|
||||
let is_fallback = input_method.is_fallback();
|
||||
if let Some(input_method) = input_method.ok() {
|
||||
inner.is_fallback = is_fallback;
|
||||
unsafe {
|
||||
let result = set_destroy_callback(&xconn, input_method.im, &inner)
|
||||
.map_err(ImeCreationError::SetDestroyCallbackFailed);
|
||||
if result.is_err() {
|
||||
let _ = close_im(&xconn, input_method.im);
|
||||
}
|
||||
result?;
|
||||
}
|
||||
inner.im = Some(input_method);
|
||||
Ok(Ime { xconn, inner })
|
||||
} else {
|
||||
Err(ImeCreationError::OpenFailure(Box::new(
|
||||
inner.potential_input_methods,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
self.inner.is_destroyed
|
||||
}
|
||||
|
||||
// This pattern is used for various methods here:
|
||||
// Ok(_) indicates that nothing went wrong internally
|
||||
// Ok(true) indicates that the action was actually performed
|
||||
// Ok(false) indicates that the action is not presently applicable
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
with_preedit: bool,
|
||||
) -> Result<bool, ImeContextCreationError> {
|
||||
let context = if self.is_destroyed() {
|
||||
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||
None
|
||||
} else {
|
||||
let im = self.inner.im.as_ref().unwrap();
|
||||
let style = if with_preedit {
|
||||
im.preedit_style
|
||||
} else {
|
||||
im.none_style
|
||||
};
|
||||
|
||||
let context = unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
im.im,
|
||||
style,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
)?
|
||||
};
|
||||
|
||||
// Check the state on the context, since it could fail to enable or disable preedit.
|
||||
let event = if matches!(style, Style::None(_)) {
|
||||
if with_preedit {
|
||||
debug!("failed to create IME context with preedit support.")
|
||||
}
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
if !with_preedit {
|
||||
debug!("failed to create IME context without preedit support.")
|
||||
}
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
|
||||
self.inner
|
||||
.event_sender
|
||||
.send((window, event))
|
||||
.expect("Failed to send enabled event");
|
||||
|
||||
Some(context)
|
||||
};
|
||||
|
||||
self.inner.contexts.insert(window, context);
|
||||
Ok(!self.is_destroyed())
|
||||
}
|
||||
|
||||
pub fn get_context(&self, window: ffi::Window) -> Option<ffi::XIC> {
|
||||
if self.is_destroyed() {
|
||||
return None;
|
||||
}
|
||||
if let Some(Some(context)) = self.inner.contexts.get(&window) {
|
||||
Some(context.ic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if let Some(Some(context)) = self.inner.contexts.remove(&window) {
|
||||
unsafe {
|
||||
self.inner.destroy_ic_if_necessary(context.ic)?;
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.focus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unfocus(&mut self, window: ffi::Window) -> Result<bool, XError> {
|
||||
if self.is_destroyed() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.unfocus(&self.xconn).map(|_| true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
context.set_spot(&self.xconn, x as _, y as _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) {
|
||||
if self.is_destroyed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) {
|
||||
if allowed == context.is_allowed() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove context for that window.
|
||||
let _ = self.remove_context(window);
|
||||
|
||||
// Create new context supporting IME input.
|
||||
let _ = self.create_context(window, allowed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ime {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.inner.destroy_all_contexts_if_necessary();
|
||||
let _ = self.inner.close_im_if_necessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,11 @@ use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
os::{
|
||||
raw::*,
|
||||
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||
},
|
||||
ptr,
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
||||
@@ -42,19 +40,14 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use libc::{self, setlocale, LC_CTYPE};
|
||||
|
||||
use atoms::*;
|
||||
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::{
|
||||
connection::RequestConnection,
|
||||
protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
},
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
xproto::{self, ConnectionExt as _},
|
||||
};
|
||||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::{
|
||||
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
|
||||
xcb_ffi::ReplyOrIdError,
|
||||
@@ -63,7 +56,6 @@ use x11rb::{
|
||||
use self::{
|
||||
dnd::{Dnd, DndState},
|
||||
event_processor::EventProcessor,
|
||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
||||
};
|
||||
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
|
||||
use crate::{
|
||||
@@ -147,15 +139,18 @@ pub struct EventLoopWindowTarget<T> {
|
||||
xconn: Arc<XConnection>,
|
||||
wm_delete_window: xproto::Atom,
|
||||
net_wm_ping: xproto::Atom,
|
||||
ime_sender: ImeSender,
|
||||
ime_sender: mpsc::Sender<ime::ImeRequest>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<Option<i32>>,
|
||||
root: xproto::Window,
|
||||
ime: RefCell<Ime>,
|
||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<ActivationToken>,
|
||||
device_events: Cell<DeviceEvents>,
|
||||
|
||||
/// State of IME.
|
||||
ime: Option<RefCell<ime::ImeData>>,
|
||||
|
||||
_marker: ::std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -201,57 +196,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
let wm_delete_window = atoms[WM_DELETE_WINDOW];
|
||||
let net_wm_ping = atoms[_NET_WM_PING];
|
||||
|
||||
// Create an event queue.
|
||||
let event_queue = Rc::new(RefCell::new(std::collections::VecDeque::with_capacity(4)));
|
||||
|
||||
let dnd = Dnd::new(Arc::clone(&xconn))
|
||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
||||
|
||||
let (ime_sender, ime_receiver) = mpsc::channel();
|
||||
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
||||
// Input methods will open successfully without setting the locale, but it won't be
|
||||
// possible to actually commit pre-edit sequences.
|
||||
unsafe {
|
||||
// Remember default locale to restore it if target locale is unsupported
|
||||
// by Xlib
|
||||
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
||||
|
||||
// Check if set locale is supported by Xlib.
|
||||
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
||||
// will fail.
|
||||
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
||||
if !locale_supported {
|
||||
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
||||
warn!(
|
||||
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
||||
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
||||
CStr::from_ptr(default_locale).to_string_lossy()
|
||||
);
|
||||
// Restore default locale
|
||||
setlocale(LC_CTYPE, default_locale);
|
||||
let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index(), &event_queue) {
|
||||
Ok(ime) => Some(ime),
|
||||
Err(e) => {
|
||||
log::error!("Failed to open IME: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
let ime = RefCell::new({
|
||||
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
|
||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
||||
panic!("Failed to open input method: {state:#?}");
|
||||
}
|
||||
result.expect("Failed to set input method destruction callback")
|
||||
});
|
||||
};
|
||||
|
||||
let randr_event_offset = xconn
|
||||
xconn
|
||||
.select_xrandr_input(root)
|
||||
.expect("Failed to query XRandR extension");
|
||||
|
||||
let xi2ext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xinput::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XInput extension")
|
||||
.expect("X server missing XInput extension");
|
||||
let xkbext = xconn
|
||||
.xcb_connection()
|
||||
.extension_information(xkb::X11_EXTENSION_NAME)
|
||||
.expect("Failed to query XKB extension")
|
||||
.expect("X server missing XKB extension");
|
||||
|
||||
// Check for XInput2 support.
|
||||
xconn
|
||||
.xcb_connection()
|
||||
@@ -303,12 +267,12 @@ impl<T: 'static> EventLoop<T> {
|
||||
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
root,
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
windows: Default::default(),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
ime: ime.map(RefCell::new),
|
||||
ime_sender,
|
||||
xconn,
|
||||
wm_delete_window,
|
||||
@@ -333,14 +297,11 @@ impl<T: 'static> EventLoop<T> {
|
||||
});
|
||||
|
||||
let event_processor = EventProcessor {
|
||||
event_queue,
|
||||
target: target.clone(),
|
||||
dnd,
|
||||
devices: Default::default(),
|
||||
randr_event_offset,
|
||||
ime_receiver,
|
||||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xkbext,
|
||||
ime_requests: ime_receiver,
|
||||
kb_state,
|
||||
num_touch: 0,
|
||||
held_key_press: None,
|
||||
@@ -623,12 +584,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
F: FnMut(Event<T>, &RootELW<T>),
|
||||
{
|
||||
let target = &self.target;
|
||||
let mut xev = MaybeUninit::uninit();
|
||||
let wt = get_xtarget(&self.target);
|
||||
|
||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
||||
let mut xev = unsafe { xev.assume_init() };
|
||||
self.event_processor.process_event(&mut xev, |event| {
|
||||
while let Some(event) = self.event_processor.poll_one_event() {
|
||||
self.event_processor.process_event(event, |event| {
|
||||
if let Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(wid),
|
||||
event: WindowEvent::RedrawRequested,
|
||||
@@ -880,8 +839,11 @@ pub enum X11Error {
|
||||
/// The XID range has been exhausted.
|
||||
XidsExhausted(IdsExhausted),
|
||||
|
||||
/// Got `null` from an Xlib function without a reason.
|
||||
UnexpectedNull(&'static str),
|
||||
/// An IME client error occurred.
|
||||
Ime(xim::ClientError),
|
||||
|
||||
/// The IME client has entered an invalid state.
|
||||
InvalidImeState(ime::InvalidImeState),
|
||||
|
||||
/// Got an invalid activation token.
|
||||
InvalidActivationToken(Vec<u8>),
|
||||
@@ -900,8 +862,9 @@ impl fmt::Display for X11Error {
|
||||
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
|
||||
X11Error::Ime(e) => write!(f, "An IME error occurred: {}", e),
|
||||
X11Error::InvalidImeState(e) => write!(f, "Invalid IME state: {}", e),
|
||||
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
|
||||
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
|
||||
X11Error::InvalidActivationToken(s) => write!(
|
||||
f,
|
||||
"Invalid activation token: {}",
|
||||
@@ -964,15 +927,6 @@ impl From<ReplyError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ime::ImeContextCreationError> for X11Error {
|
||||
fn from(value: ime::ImeContextCreationError) -> Self {
|
||||
match value {
|
||||
ime::ImeContextCreationError::XError(e) => e.into(),
|
||||
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReplyOrIdError> for X11Error {
|
||||
fn from(value: ReplyOrIdError) -> Self {
|
||||
match value {
|
||||
@@ -983,6 +937,18 @@ impl From<ReplyOrIdError> for X11Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xim::ClientError> for X11Error {
|
||||
fn from(e: xim::ClientError) -> Self {
|
||||
match e {
|
||||
xim::ClientError::Other(other) => match other.downcast::<X11Error>() {
|
||||
Ok(x11) => *x11,
|
||||
Err(other) => X11Error::Ime(xim::ClientError::Other(other)),
|
||||
},
|
||||
e => X11Error::Ime(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The underlying x11rb connection that we are using.
|
||||
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
@@ -1001,34 +967,6 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
|
||||
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
|
||||
struct GenericEventCookie<'a> {
|
||||
xconn: &'a XConnection,
|
||||
cookie: ffi::XGenericEventCookie,
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
|
||||
unsafe {
|
||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
|
||||
Some(GenericEventCookie { xconn, cookie })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for GenericEventCookie<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
|
||||
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
|
||||
}
|
||||
@@ -1130,8 +1068,15 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
||||
/// Convert the raw X11 representation for a 32-bit fixed point to a double.
|
||||
#[inline]
|
||||
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
||||
(fp as f64) / ((1 << 16) as f64)
|
||||
}
|
||||
|
||||
/// Conver the raw X11 representation for a 64-bit fixed point number to a double.
|
||||
#[inline]
|
||||
fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 {
|
||||
let xinput::Fp3232 { integral, frac } = fp;
|
||||
integral as f64 + (frac as f64 / (1u64 << 32) as f64)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::{slice, str};
|
||||
use x11rb::protocol::{
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xkb,
|
||||
@@ -9,11 +8,6 @@ use super::*;
|
||||
pub const VIRTUAL_CORE_POINTER: u16 = 2;
|
||||
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(
|
||||
&self,
|
||||
@@ -60,52 +54,4 @@ impl XConnection {
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: *mut u8,
|
||||
size: usize,
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer as *mut c_char,
|
||||
size as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
// If the buffer overflows, we'll make a new one on the heap.
|
||||
let mut vec;
|
||||
|
||||
let (_, status, count) =
|
||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||
|
||||
let bytes = if status == ffi::XBufferOverflow {
|
||||
vec = Vec::with_capacity(count as usize);
|
||||
let (_, _, new_count) =
|
||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||
debug_assert_eq!(count, new_count);
|
||||
|
||||
unsafe { vec.set_len(count as usize) };
|
||||
&vec[..count as usize]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||
};
|
||||
|
||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,6 @@ pub(crate) struct XSmartPointer<'a, T> {
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer { xconn, ptr })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm
|
||||
|
||||
use std::{
|
||||
mem::{self, MaybeUninit},
|
||||
ops::BitAnd,
|
||||
os::raw::*,
|
||||
};
|
||||
|
||||
@@ -34,13 +33,6 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is impoartant, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
mem::replace,
|
||||
os::raw::*,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
sync::{mpsc, Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
@@ -43,9 +43,9 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
ffi,
|
||||
ime::ImeRequest,
|
||||
util::{self, CustomCursor, SelectedCursor},
|
||||
CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId,
|
||||
XConnection,
|
||||
CookieResultExt, EventLoopWindowTarget, VoidCookie, WindowId, XConnection,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -135,7 +135,7 @@ pub(crate) struct UnownedWindow {
|
||||
cursor_grabbed_mode: Mutex<CursorGrabMode>,
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
cursor_visible: Mutex<bool>,
|
||||
ime_sender: Mutex<ImeSender>,
|
||||
ime_sender: Mutex<mpsc::Sender<ImeRequest>>,
|
||||
pub shared_state: Mutex<SharedState>,
|
||||
redraw_sender: WakeSender<WindowId>,
|
||||
activation_sender: WakeSender<super::ActivationToken>,
|
||||
@@ -547,11 +547,10 @@ impl UnownedWindow {
|
||||
.ignore_error();
|
||||
|
||||
{
|
||||
let result = event_loop
|
||||
.ime
|
||||
.borrow_mut()
|
||||
.create_context(window.xwindow as ffi::Window, false);
|
||||
leap!(result);
|
||||
if let Some(ime) = event_loop.ime.as_ref() {
|
||||
let result = ime.borrow_mut().create_context(window.xwindow, false, None);
|
||||
leap!(result);
|
||||
}
|
||||
}
|
||||
|
||||
// These properties must be set after mapping
|
||||
@@ -1792,11 +1791,11 @@ impl UnownedWindow {
|
||||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) {
|
||||
let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
|
||||
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
|
||||
self.xwindow as ffi::Window,
|
||||
x,
|
||||
y,
|
||||
));
|
||||
let _ = self
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Position(self.xwindow, x, y));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1805,7 +1804,7 @@ impl UnownedWindow {
|
||||
.ime_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
|
||||
.send(ImeRequest::Allow(self.xwindow, allowed));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -91,6 +91,14 @@ impl XConnection {
|
||||
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
|
||||
};
|
||||
|
||||
// Make sure Xlib knows XCB is handling events.
|
||||
unsafe {
|
||||
(xlib_xcb.XSetEventQueueOwner)(
|
||||
display,
|
||||
x11_dl::xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the default screen.
|
||||
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user