x11: implement true cursor area with XNArea attribute
This commit is contained in:
parent
16d5f46db1
commit
a0464ae83b
7 changed files with 46 additions and 36 deletions
|
|
@ -73,6 +73,7 @@ changelog entry.
|
|||
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
|
||||
- On Windows, add `IconExtWindows::from_resource_name`.
|
||||
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
|
||||
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ impl EventProcessor {
|
|||
|
||||
let ime = ime.get_mut();
|
||||
match request {
|
||||
ImeRequest::Position(window_id, x, y) => {
|
||||
ime.send_xim_spot(window_id, x, y);
|
||||
ImeRequest::Area(window_id, x, y, w, h) => {
|
||||
ime.send_xim_area(window_id, x, y, w, h);
|
||||
},
|
||||
ImeRequest::Allow(window_id, allowed) => {
|
||||
ime.set_ime_allowed(window_id, allowed);
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
|||
|
||||
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);
|
||||
let area = old_context.as_ref().map(|old_context| old_context.ic_area);
|
||||
|
||||
// Check if the IME was allowed on that context.
|
||||
let is_allowed =
|
||||
|
|
@ -128,7 +128,7 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
|||
xconn,
|
||||
&new_im,
|
||||
*window,
|
||||
spot,
|
||||
area,
|
||||
(*inner).event_sender.clone(),
|
||||
is_allowed,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::error::Error;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_short;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, ptr};
|
||||
|
||||
|
|
@ -196,7 +195,7 @@ struct ImeContextClientData {
|
|||
// through `ImeInner`.
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) ic_area: ffi::XRectangle,
|
||||
pub(crate) allowed: bool,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
|
||||
// from there we keep the pointer to automatically deallocate it.
|
||||
|
|
@ -208,7 +207,7 @@ impl ImeContext {
|
|||
xconn: &Arc<XConnection>,
|
||||
im: &InputMethod,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
ic_area: Option<ffi::XRectangle>,
|
||||
event_sender: ImeEventSender,
|
||||
allowed: bool,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
|
|
@ -244,14 +243,14 @@ impl ImeContext {
|
|||
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
ic_area: ffi::XRectangle { x: 0, y: 0, width: 0, height: 0 },
|
||||
allowed,
|
||||
_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)
|
||||
// Set the preedit cursor area, if it's present.
|
||||
if let Some(ic_area) = ic_area {
|
||||
context.set_area(xconn, ic_area.x, ic_area.y, ic_area.width, ic_area.height);
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
|
|
@ -355,17 +354,31 @@ impl ImeContext {
|
|||
self.allowed
|
||||
}
|
||||
|
||||
// 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 {
|
||||
/// Set the spot and area for preedit text.
|
||||
///
|
||||
/// This functionality depends on the libx11 version.
|
||||
/// - Until libx11 1.8.2, XNSpotLocation was blocked by libx11 in On-The-Spot mode.
|
||||
/// - Until libx11 1.8.11, XNArea was blocked by libx11 in On-The-Spot mode.
|
||||
///
|
||||
/// Use of this information is discretionary by input method servers,
|
||||
/// and some may not use it by default, even if they have support.
|
||||
pub(crate) fn set_area(
|
||||
&mut self,
|
||||
xconn: &Arc<XConnection>,
|
||||
x: i16,
|
||||
y: i16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) {
|
||||
let ic_area = ffi::XRectangle { x, y, width, height };
|
||||
|
||||
if !self.is_allowed() || self.ic_area == ic_area {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ic_spot = ffi::XPoint { x, y };
|
||||
self.ic_area = ic_area;
|
||||
let ic_spot =
|
||||
ffi::XPoint { x: x.saturating_add(width as i16), y: y.saturating_add(height as i16) };
|
||||
|
||||
unsafe {
|
||||
let preedit_attr = util::memory::XSmartPointer::new(
|
||||
|
|
@ -373,7 +386,9 @@ impl ImeContext {
|
|||
(xconn.xlib.XVaCreateNestedList)(
|
||||
0,
|
||||
ffi::XNSpotLocation_0.as_ptr(),
|
||||
&self.ic_spot,
|
||||
&ic_spot,
|
||||
ffi::XNArea_0.as_ptr(),
|
||||
&self.ic_area,
|
||||
ptr::null_mut::<()>(),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ 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),
|
||||
/// Set IME preedit area for given `window_id`.
|
||||
Area(ffi::Window, i16, i16, u16, u16),
|
||||
|
||||
/// Allow IME input for the given `window_id`.
|
||||
Allow(ffi::Window, bool),
|
||||
|
|
@ -192,12 +192,12 @@ impl Ime {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) {
|
||||
pub fn send_xim_area(&mut self, window: ffi::Window, x: i16, y: i16, w: u16, h: u16) {
|
||||
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 _);
|
||||
context.set_area(&self.xconn, x as _, y as _, w as _, h as _);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2048,17 +2048,13 @@ impl UnownedWindow {
|
|||
#[inline]
|
||||
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
|
||||
let PhysicalPosition { x, y } = spot.to_physical::<i16>(self.scale_factor());
|
||||
let PhysicalSize { width, height } = size.to_physical::<i16>(self.scale_factor());
|
||||
// We only currently support reporting a caret position via XIM.
|
||||
// No IM servers currently process preedit area information from XIM clients
|
||||
// and it is unclear this is even part of the standard protocol.
|
||||
// Fcitx and iBus both assume that the position reported is at the insertion
|
||||
// caret, and by default will place the candidate window under and to the
|
||||
// right of the reported point.
|
||||
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
|
||||
let PhysicalSize { width, height } = size.to_physical::<u16>(self.scale_factor());
|
||||
let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Area(
|
||||
self.xwindow as ffi::Window,
|
||||
x.saturating_add(width),
|
||||
y.saturating_add(height),
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1064,8 +1064,6 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **X11:** Area is not supported, only position. The bottom-right corner of the provided
|
||||
/// area is reported as the position.
|
||||
/// - **iOS / Android / Web / Orbital:** Unsupported.
|
||||
///
|
||||
/// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue