zed_xim/
xlib.rs

1//! Provides a wrapper around Xlib (through the [`x11-dl`] crate) that allows to use Xlib as a
2//! client for XIM.
3//!
4//! Note that it is generally discouraged to use Xlib in the modern era.
5
6use crate::AHashMap;
7use alloc::vec::Vec;
8use std::ffi::CStr;
9use std::mem::MaybeUninit;
10use std::rc::Rc;
11use std::sync::Arc;
12use std::{convert::TryInto, os::raw::c_long};
13
14use crate::{
15    client::{handle_request, ClientCore, ClientError, ClientHandler},
16    Atoms,
17};
18use x11_dl::xlib;
19use xim_parser::{AttributeName, Request, XimWrite};
20
21impl<X: XlibRef> ClientCore for XlibClient<X> {
22    type XEvent = xlib::XKeyEvent;
23
24    #[inline]
25    fn ic_attributes(&self) -> &AHashMap<AttributeName, u16> {
26        &self.ic_attributes
27    }
28
29    #[inline]
30    fn im_attributes(&self) -> &AHashMap<AttributeName, u16> {
31        &self.im_attributes
32    }
33
34    #[inline]
35    fn serialize_event(&self, xev: &Self::XEvent) -> xim_parser::XEvent {
36        xim_parser::XEvent {
37            response_type: xev.type_ as u8,
38            detail: xev.keycode as u8,
39            sequence: xev.serial as _,
40            time: xev.time as u32,
41            root: xev.root as u32,
42            event: xev.window as u32,
43            child: xev.subwindow as u32,
44            root_x: xev.x_root as i16,
45            root_y: xev.y_root as i16,
46            event_x: xev.x as i16,
47            event_y: xev.y as i16,
48            state: xev.state as u16,
49            same_screen: xev.same_screen != 0,
50        }
51    }
52
53    #[inline]
54    fn deserialize_event(&self, xev: &xim_parser::XEvent) -> Self::XEvent {
55        xlib::XKeyEvent {
56            type_: xev.response_type as _,
57            keycode: xev.detail as _,
58            serial: xev.sequence as _,
59            time: xev.time as _,
60            root: xev.root as _,
61            window: xev.event as _,
62            subwindow: xev.child as _,
63            x_root: xev.root_x as _,
64            y_root: xev.root_y as _,
65            x: xev.event_x as _,
66            y: xev.event_y as _,
67            state: xev.state as _,
68            same_screen: xev.same_screen as i32,
69            display: self.display,
70            send_event: 0,
71        }
72    }
73
74    #[inline]
75    fn send_req(&mut self, req: xim_parser::Request) -> Result<(), ClientError> {
76        self.send_req_impl(req);
77        Ok(())
78    }
79
80    fn set_attrs(&mut self, ic_attrs: Vec<xim_parser::Attr>, im_attrs: Vec<xim_parser::Attr>) {
81        for im_attr in im_attrs {
82            self.im_attributes.insert(im_attr.name, im_attr.id);
83        }
84
85        for ic_attr in ic_attrs {
86            self.ic_attributes.insert(ic_attr.name, ic_attr.id);
87        }
88    }
89}
90
91impl<'a> XlibRef for &'a xlib::Xlib {
92    fn xlib(&self) -> &xlib::Xlib {
93        self
94    }
95}
96
97impl<X> XlibRef for Rc<X>
98where
99    X: XlibRef,
100{
101    fn xlib(&self) -> &xlib::Xlib {
102        (**self).xlib()
103    }
104}
105
106impl<X> XlibRef for Arc<X>
107where
108    X: XlibRef,
109{
110    fn xlib(&self) -> &xlib::Xlib {
111        (**self).xlib()
112    }
113}
114
115impl XlibRef for xlib::Xlib {
116    fn xlib(&self) -> &xlib::Xlib {
117        self
118    }
119}
120
121pub trait XlibRef {
122    fn xlib(&self) -> &xlib::Xlib;
123}
124
125pub struct XlibClient<X: XlibRef> {
126    x: X,
127    display: *mut xlib::Display,
128    im_window: xlib::Window,
129    server_owner_window: xlib::Window,
130    server_atom: xlib::Atom,
131    atoms: Atoms<xlib::Atom>,
132    transport_max: usize,
133    client_window: xlib::Window,
134    im_attributes: AHashMap<AttributeName, u16>,
135    ic_attributes: AHashMap<AttributeName, u16>,
136    buf: Vec<u8>,
137    sequence: u16,
138}
139
140impl<X: XlibRef> XlibClient<X> {
141    /// Initialize a new `XlibClient` from an Xlib connection.
142    ///
143    /// # Safety
144    ///
145    /// The `display` pointer must be a valid Xlib display.
146    pub unsafe fn init(
147        x: X,
148        display: *mut xlib::Display,
149        im_name: Option<&str>,
150    ) -> Result<Self, ClientError> {
151        let xlib = x.xlib();
152        let root = (xlib.XDefaultRootWindow)(display);
153        let client_window = (xlib.XCreateSimpleWindow)(display, root, 0, 0, 1, 1, 0, 0, 0);
154
155        let var = std::env::var("XMODIFIERS").ok();
156        let var = var.as_ref().and_then(|n| n.strip_prefix("@im="));
157        let im_name = im_name.or(var).ok_or(ClientError::NoXimServer)?;
158
159        let atoms = Atoms::new_null::<ClientError, _>(|name| {
160            let atom = (xlib.XInternAtom)(display, name.as_ptr() as *const _, 0);
161            if atom == 0 {
162                Err(ClientError::InvalidReply)
163            } else {
164                Ok(atom)
165            }
166        })?;
167
168        let mut ty = MaybeUninit::uninit();
169        let mut format = MaybeUninit::uninit();
170        let mut items = MaybeUninit::uninit();
171        let mut bytes = MaybeUninit::uninit();
172        let mut prop = MaybeUninit::uninit();
173
174        let code = (xlib.XGetWindowProperty)(
175            display,
176            root,
177            atoms.XIM_SERVERS,
178            0,
179            i64::MAX,
180            xlib::False,
181            xlib::XA_ATOM,
182            ty.as_mut_ptr(),
183            format.as_mut_ptr(),
184            items.as_mut_ptr(),
185            bytes.as_mut_ptr(),
186            prop.as_mut_ptr(),
187        );
188
189        if code != 0 {
190            return Err(ClientError::InvalidReply);
191        }
192
193        let ty = ty.assume_init();
194        let format = format.assume_init();
195        let items = items.assume_init();
196        let _bytes = bytes.assume_init();
197        let prop = prop.assume_init() as *mut xlib::Atom;
198
199        if ty != xlib::XA_ATOM || format != 32 {
200            Err(ClientError::InvalidReply)
201        } else {
202            for i in 0..items {
203                let server_atom = prop.add(i as usize).read();
204                let server_owner = (xlib.XGetSelectionOwner)(display, server_atom);
205                let name_ptr = (xlib.XGetAtomName)(display, server_atom);
206                let name = CStr::from_ptr(name_ptr);
207                let name = match name.to_str() {
208                    Ok(s) => s,
209                    _ => continue,
210                };
211
212                if let Some(name) = name.strip_prefix("@server=") {
213                    if name == im_name {
214                        (xlib.XConvertSelection)(
215                            display,
216                            server_atom,
217                            atoms.TRANSPORT,
218                            atoms.TRANSPORT,
219                            client_window,
220                            xlib::CurrentTime,
221                        );
222                        (xlib.XFlush)(display);
223                        (xlib.XFree)(name_ptr as _);
224                        (xlib.XFree)(prop as _);
225
226                        return Ok(Self {
227                            atoms,
228                            client_window,
229                            server_atom,
230                            server_owner_window: server_owner,
231                            im_window: 0,
232                            transport_max: 0,
233                            display,
234                            x,
235                            ic_attributes: AHashMap::with_hasher(Default::default()),
236                            im_attributes: AHashMap::with_hasher(Default::default()),
237                            buf: Vec::with_capacity(1024),
238                            sequence: 0,
239                        });
240                    }
241                } else {
242                    (xlib.XFree)(name_ptr as _);
243                }
244            }
245
246            (xlib.XFree)(prop as _);
247
248            Err(ClientError::NoXimServer)
249        }
250    }
251
252    /// Filter an event and call the handler if it is relevant.
253    ///
254    /// # Safety
255    ///
256    /// The event `e` must be a valid Xlib event.
257    pub unsafe fn filter_event(
258        &mut self,
259        e: &xlib::XEvent,
260        handler: &mut impl ClientHandler<Self>,
261    ) -> Result<bool, ClientError> {
262        match e.get_type() {
263            xlib::SelectionNotify if e.selection.requestor == self.client_window => {
264                let mut ty = MaybeUninit::uninit();
265                let mut format = MaybeUninit::uninit();
266                let mut items = MaybeUninit::uninit();
267                let mut bytes = MaybeUninit::uninit();
268                let mut prop = MaybeUninit::uninit();
269                (self.x.xlib().XGetWindowProperty)(
270                    self.display,
271                    self.client_window,
272                    self.atoms.TRANSPORT,
273                    0,
274                    i64::MAX,
275                    xlib::True,
276                    self.atoms.TRANSPORT,
277                    ty.as_mut_ptr(),
278                    format.as_mut_ptr(),
279                    items.as_mut_ptr(),
280                    bytes.as_mut_ptr(),
281                    prop.as_mut_ptr(),
282                );
283
284                let _ty = ty.assume_init();
285                let _format = format.assume_init();
286                let items = items.assume_init();
287                let _bytes = bytes.assume_init();
288                let prop = prop.assume_init();
289
290                if e.selection.property == self.atoms.LOCALES {
291                    // TODO: set locale
292                    self.xconnect();
293                } else if e.selection.property == self.atoms.TRANSPORT {
294                    let transport = std::slice::from_raw_parts(prop, items as usize);
295
296                    if !transport.starts_with(b"@transport=X/") {
297                        return Err(ClientError::UnsupportedTransport);
298                    }
299
300                    (self.x.xlib().XConvertSelection)(
301                        self.display,
302                        self.server_atom,
303                        self.atoms.LOCALES,
304                        self.atoms.LOCALES,
305                        self.client_window,
306                        xlib::CurrentTime,
307                    );
308                }
309
310                (self.x.xlib().XFree)(prop as _);
311
312                Ok(true)
313            }
314            xlib::ClientMessage if e.client_message.window == self.client_window => {
315                if e.client_message.message_type == self.atoms.XIM_XCONNECT {
316                    let [im_window, major, minor, max, _]: [c_long; 5] =
317                        e.client_message.data.as_longs().try_into().unwrap();
318
319                    log::info!(
320                        "XConnected server on {}, transport version: {}.{}, TRANSPORT_MAX: {}",
321                        im_window,
322                        major,
323                        minor,
324                        max
325                    );
326
327                    self.im_window = im_window as xlib::Window;
328                    self.transport_max = max as usize;
329                    self.send_req(Request::Connect {
330                        client_major_protocol_version: 1,
331                        client_minor_protocol_version: 0,
332                        endian: xim_parser::Endian::Native,
333                        client_auth_protocol_names: Vec::new(),
334                    })?;
335
336                    Ok(true)
337                } else if e.client_message.message_type == self.atoms.XIM_PROTOCOL {
338                    self.handle_xim_protocol(&e.client_message, handler)?;
339                    Ok(true)
340                } else {
341                    Ok(false)
342                }
343            }
344            _ => Ok(false),
345        }
346    }
347
348    fn handle_xim_protocol(
349        &mut self,
350        msg: &xlib::XClientMessageEvent,
351        handler: &mut impl ClientHandler<Self>,
352    ) -> Result<(), ClientError> {
353        if msg.format == 32 {
354            let length = msg.data.get_long(0);
355            let atom = msg.data.get_long(1);
356
357            let mut ty = MaybeUninit::uninit();
358            let mut format = MaybeUninit::uninit();
359            let mut items = MaybeUninit::uninit();
360            let mut bytes = MaybeUninit::uninit();
361            let mut prop = MaybeUninit::uninit();
362
363            unsafe {
364                let code = (self.x.xlib().XGetWindowProperty)(
365                    self.display,
366                    msg.window,
367                    atom as _,
368                    0,
369                    length,
370                    xlib::True,
371                    0,
372                    ty.as_mut_ptr(),
373                    format.as_mut_ptr(),
374                    items.as_mut_ptr(),
375                    bytes.as_mut_ptr(),
376                    prop.as_mut_ptr(),
377                );
378
379                if code != 0 {
380                    return Err(ClientError::InvalidReply);
381                }
382
383                let _ty = ty.assume_init();
384                let _format = format.assume_init();
385                let items = items.assume_init();
386                let _bytes = bytes.assume_init();
387                let prop = prop.assume_init();
388
389                // handle fcitx4 occasionally sending empty reply
390                if _bytes == 0 {
391                    return Err(ClientError::InvalidReply);
392                }
393
394                let data = std::slice::from_raw_parts(prop, items as usize);
395
396                let req = xim_parser::read(data)?;
397
398                handle_request(self, handler, req)?;
399
400                (self.x.xlib().XFree)(prop as _);
401            }
402        } else if msg.format == 8 {
403            let bytes = msg.data.as_bytes();
404            let data: &[u8] =
405                unsafe { std::slice::from_raw_parts(bytes.as_ptr() as _, bytes.len()) };
406            let req = xim_parser::read(data)?;
407            handle_request(self, handler, req)?;
408        }
409
410        Ok(())
411    }
412
413    fn xconnect(&mut self) {
414        let mut ev = xlib::XClientMessageEvent {
415            display: self.display,
416            data: [self.client_window, 0, 0, 0, 0].into(),
417            format: 32,
418            message_type: self.atoms.XIM_XCONNECT,
419            serial: 0,
420            type_: xlib::ClientMessage,
421            send_event: xlib::True,
422            window: self.server_owner_window,
423        }
424        .into();
425
426        unsafe {
427            (self.x.xlib().XSendEvent)(
428                self.display,
429                self.server_owner_window,
430                xlib::False,
431                xlib::NoEventMask,
432                &mut ev,
433            );
434        }
435    }
436
437    fn send_req_impl(&mut self, req: Request) {
438        if log::log_enabled!(log::Level::Trace) {
439            log::trace!("->: {:?}", req);
440        } else {
441            log::debug!("->: {}", req.name());
442        }
443
444        self.buf.resize(req.size(), 0);
445        xim_parser::write(&req, &mut self.buf);
446
447        if self.buf.len() < self.transport_max {
448            if self.buf.len() > 20 {
449                todo!("multi-CM");
450            }
451            self.buf.resize(20, 0);
452            let buf: [u8; 20] = self.buf.as_slice().try_into().unwrap();
453            let mut ev = xlib::XClientMessageEvent {
454                type_: xlib::ClientMessage,
455                display: self.display,
456                message_type: self.atoms.XIM_PROTOCOL,
457                data: buf.into(),
458                format: 8,
459                serial: 0,
460                send_event: xlib::True,
461                window: self.im_window,
462            }
463            .into();
464            unsafe {
465                (self.x.xlib().XSendEvent)(
466                    self.display,
467                    self.im_window,
468                    xlib::False,
469                    xlib::NoEventMask,
470                    &mut ev,
471                );
472            }
473        } else {
474            let name = alloc::format!("_XIM_DATA_{}\0", self.sequence);
475            self.sequence += 1;
476            let prop =
477                unsafe { (self.x.xlib().XInternAtom)(self.display, name.as_ptr().cast(), 0) };
478
479            unsafe {
480                (self.x.xlib().XChangeProperty)(
481                    self.display,
482                    self.im_window,
483                    prop,
484                    xlib::XA_STRING,
485                    8,
486                    xlib::PropModeAppend,
487                    self.buf.as_ptr(),
488                    self.buf.len() as _,
489                );
490            }
491            let mut ev = xlib::XClientMessageEvent {
492                type_: xlib::ClientMessage,
493                display: self.display,
494                message_type: self.atoms.XIM_PROTOCOL,
495                data: [self.buf.len() as _, prop, 0, 0, 0].into(),
496                format: 32,
497                serial: 0,
498                send_event: xlib::True,
499                window: self.im_window,
500            }
501            .into();
502            unsafe {
503                (self.x.xlib().XSendEvent)(
504                    self.display,
505                    self.im_window,
506                    xlib::False,
507                    xlib::NoEventMask,
508                    &mut ev,
509                );
510            }
511        }
512        self.buf.clear();
513    }
514}