zed_xim/
x11rb.rs

1//! Provides an implementation of XIM using [`x11rb`] as a transport.
2//!
3//! Wrap your `Connection` in an [`X11rbClient`] or [`X11rbServer`] and use it as a
4//! client or server.
5//!
6//! [`x11rb`]: https://crates.io/crates/x11rb
7
8use alloc::format;
9use alloc::string::String;
10use alloc::vec::Vec;
11use std::{convert::TryInto, rc::Rc, sync::Arc};
12use x11rb::protocol::xproto::EventMask;
13
14#[cfg(feature = "x11rb-client")]
15use crate::client::{
16    handle_request as client_handle_request, ClientCore, ClientError, ClientHandler,
17};
18#[cfg(feature = "x11rb-server")]
19use crate::server::{ServerCore, ServerError, ServerHandler, XimConnection, XimConnections};
20#[cfg(feature = "x11rb-client")]
21use crate::AHashMap;
22#[cfg(feature = "x11rb-client")]
23use xim_parser::{Attr, AttributeName};
24
25use crate::Atoms;
26
27#[cfg(feature = "x11rb-xcb")]
28use x11rb::xcb_ffi::XCBConnection;
29
30#[allow(unused_imports)]
31use x11rb::{
32    connection::Connection,
33    errors::{ConnectError, ConnectionError, ParseError, ReplyError, ReplyOrIdError},
34    protocol::{
35        xproto::{
36            Atom, AtomEnum, ClientMessageEvent, ConnectionExt, KeyPressEvent, PropMode, Screen,
37            SelectionNotifyEvent, SelectionRequestEvent, Window, WindowClass, CLIENT_MESSAGE_EVENT,
38            SELECTION_NOTIFY_EVENT,
39        },
40        Event,
41    },
42    rust_connection::RustConnection,
43    wrapper::ConnectionExt as _,
44    COPY_DEPTH_FROM_PARENT, CURRENT_TIME,
45};
46
47use xim_parser::{Request, XimWrite};
48
49macro_rules! convert_error {
50    ($($ty:ty,)+) => {
51        $(
52            #[cfg(feature = "x11rb-client")]
53            impl From<$ty> for ClientError {
54                fn from(err: $ty) -> Self {
55                    ClientError::Other(err.into())
56                }
57            }
58
59            #[cfg(feature = "x11rb-server")]
60            impl From<$ty> for ServerError {
61                fn from(err: $ty) -> Self {
62                    ServerError::Other(err.into())
63                }
64            }
65        )+
66    };
67}
68
69convert_error!(
70    ConnectError,
71    ConnectionError,
72    ReplyError,
73    ReplyOrIdError,
74    ParseError,
75);
76
77pub trait HasConnection {
78    type Connection: Connection + ConnectionExt;
79
80    fn conn(&self) -> &Self::Connection;
81}
82
83#[cfg(feature = "x11rb-xcb")]
84impl HasConnection for XCBConnection {
85    type Connection = Self;
86
87    #[inline(always)]
88    fn conn(&self) -> &Self::Connection {
89        self
90    }
91}
92
93impl HasConnection for RustConnection {
94    type Connection = Self;
95
96    #[inline(always)]
97    fn conn(&self) -> &Self::Connection {
98        self
99    }
100}
101
102#[cfg(feature = "x11rb-client")]
103impl<C: HasConnection> HasConnection for X11rbClient<C> {
104    type Connection = C::Connection;
105
106    #[inline(always)]
107    fn conn(&self) -> &Self::Connection {
108        self.has_conn.conn()
109    }
110}
111
112#[cfg(feature = "x11rb-server")]
113impl<C: HasConnection> HasConnection for X11rbServer<C> {
114    type Connection = C::Connection;
115
116    #[inline(always)]
117    fn conn(&self) -> &Self::Connection {
118        self.has_conn.conn()
119    }
120}
121
122impl<'x, C: HasConnection> HasConnection for &'x C {
123    type Connection = C::Connection;
124
125    #[inline(always)]
126    fn conn(&self) -> &Self::Connection {
127        (**self).conn()
128    }
129}
130
131impl<C: HasConnection> HasConnection for Rc<C> {
132    type Connection = C::Connection;
133
134    #[inline(always)]
135    fn conn(&self) -> &Self::Connection {
136        (**self).conn()
137    }
138}
139
140impl<C: HasConnection> HasConnection for Arc<C> {
141    type Connection = C::Connection;
142
143    #[inline(always)]
144    fn conn(&self) -> &Self::Connection {
145        (**self).conn()
146    }
147}
148
149#[cfg(feature = "x11rb-server")]
150pub struct X11rbServer<C: HasConnection> {
151    has_conn: C,
152    locale_data: String,
153    im_win: Window,
154    atoms: Atoms<Atom>,
155    buf: Vec<u8>,
156    sequence: u16,
157}
158
159#[cfg(feature = "x11rb-server")]
160impl<C: HasConnection> X11rbServer<C> {
161    pub fn init(
162        has_conn: C,
163        screen_num: usize,
164        im_name: &str,
165        locales: &str,
166    ) -> Result<Self, ServerError> {
167        let im_name = format!("@server={}", im_name);
168        let conn = has_conn.conn();
169        let screen = &conn.setup().roots[screen_num];
170        let im_win = conn.generate_id()?;
171        conn.create_window(
172            COPY_DEPTH_FROM_PARENT,
173            im_win,
174            screen.root,
175            0,
176            0,
177            1,
178            1,
179            0,
180            WindowClass::INPUT_ONLY,
181            screen.root_visual,
182            &Default::default(),
183        )?;
184        let atoms = Atoms::new::<ServerError, _>(|name| {
185            Ok(conn.intern_atom(false, name.as_bytes())?.reply()?.atom)
186        })?;
187
188        let reply = conn
189            .get_property(
190                false,
191                screen.root,
192                atoms.XIM_SERVERS,
193                AtomEnum::ATOM,
194                0,
195                u32::MAX,
196            )?
197            .reply()?;
198
199        if reply.type_ != x11rb::NONE && (reply.type_ != u32::from(AtomEnum::ATOM)) {
200            return Err(ServerError::InvalidReply);
201        }
202
203        let server_name = conn.intern_atom(false, im_name.as_bytes())?.reply()?.atom;
204
205        let mut found = false;
206
207        if reply.type_ != x11rb::NONE {
208            for prop in reply.value32().ok_or(ServerError::InvalidReply)? {
209                if prop == server_name {
210                    log::info!("Found previous XIM_SERVER it will overrided");
211                    found = true;
212                }
213            }
214        }
215
216        // override owner
217        conn.set_selection_owner(im_win, server_name, x11rb::CURRENT_TIME)?;
218
219        if !found {
220            conn.change_property32(
221                PropMode::PREPEND,
222                screen.root,
223                atoms.XIM_SERVERS,
224                AtomEnum::ATOM,
225                &[server_name],
226            )?;
227        }
228
229        conn.flush()?;
230
231        log::info!("Start server win: {}", im_win);
232
233        Ok(Self {
234            has_conn,
235            locale_data: format!("@locale={}", locales),
236            im_win,
237            atoms,
238            buf: Vec::with_capacity(1024),
239            sequence: 0,
240        })
241    }
242
243    pub fn filter_event<T>(
244        &mut self,
245        e: &Event,
246        connections: &mut XimConnections<T>,
247        handler: &mut impl ServerHandler<Self, InputContextData = T>,
248    ) -> Result<bool, ServerError> {
249        match e {
250            Event::SelectionRequest(req) if req.owner == self.im_win => {
251                if req.property == self.atoms.LOCALES {
252                    log::trace!("Selection notify locale");
253                    self.send_selection_notify(req, &self.locale_data)?;
254                } else if req.property == self.atoms.TRANSPORT {
255                    log::trace!("Selection notify transport");
256                    self.send_selection_notify(req, "@transport=X/")?;
257                }
258                Ok(true)
259            }
260            Event::ClientMessage(msg) => {
261                if msg.type_ == self.atoms.XIM_XCONNECT {
262                    let com_win = self.conn().generate_id()?;
263                    self.conn().create_window(
264                        COPY_DEPTH_FROM_PARENT,
265                        com_win,
266                        self.im_win,
267                        0,
268                        0,
269                        1,
270                        1,
271                        0,
272                        WindowClass::INPUT_ONLY,
273                        0,
274                        &Default::default(),
275                    )?;
276                    let client_win = msg.data.as_data32()[0];
277                    log::info!("XConnected with {}", client_win);
278                    self.conn().send_event(
279                        false,
280                        client_win,
281                        EventMask::NO_EVENT,
282                        ClientMessageEvent {
283                            format: 32,
284                            type_: self.atoms.XIM_XCONNECT,
285                            data: [com_win, 0, 0, 0, 0].into(),
286                            response_type: CLIENT_MESSAGE_EVENT,
287                            sequence: 0,
288                            window: client_win,
289                        },
290                    )?;
291                    self.conn().flush()?;
292                    connections.new_connection(com_win, client_win);
293                } else if msg.type_ == self.atoms.XIM_PROTOCOL {
294                    if let Some(connection) = connections.get_connection(msg.window) {
295                        self.handle_xim_protocol(msg, connection, handler)?;
296                        if connection.disconnected {
297                            connections.remove_connection(msg.window);
298                        }
299                    } else {
300                        log::warn!("Unknown connection");
301                    }
302                }
303
304                Ok(true)
305            }
306            _ => Ok(false),
307        }
308    }
309
310    fn handle_xim_protocol<T>(
311        &mut self,
312        msg: &ClientMessageEvent,
313        connection: &mut XimConnection<T>,
314        handler: &mut impl ServerHandler<Self, InputContextData = T>,
315    ) -> Result<(), ServerError> {
316        if msg.format == 32 {
317            let [length, atom, ..] = msg.data.as_data32();
318            let data = self
319                .conn()
320                .get_property(true, msg.window, atom, AtomEnum::ANY, 0, length)?
321                .reply()?
322                .value;
323            let req = xim_parser::read(&data)?;
324            connection.handle_request(self, req, handler)
325        } else {
326            let req = xim_parser::read(&msg.data.as_data8())?;
327            connection.handle_request(self, req, handler)
328        }
329    }
330
331    fn send_selection_notify(
332        &self,
333        req: &SelectionRequestEvent,
334        data: &str,
335    ) -> Result<(), ServerError> {
336        let e = SelectionNotifyEvent {
337            response_type: SELECTION_NOTIFY_EVENT,
338            property: req.property,
339            time: req.time,
340            target: req.target,
341            selection: req.selection,
342            requestor: req.requestor,
343            sequence: 0,
344        };
345
346        self.conn().change_property8(
347            PropMode::REPLACE,
348            req.requestor,
349            req.property,
350            req.target,
351            data.as_bytes(),
352        )?;
353        self.conn()
354            .send_event(false, req.requestor, EventMask::NO_EVENT, e)?;
355        self.conn().flush()?;
356
357        Ok(())
358    }
359}
360
361#[cfg(feature = "x11rb-server")]
362impl<C: HasConnection> ServerCore for X11rbServer<C> {
363    type XEvent = KeyPressEvent;
364
365    fn send_req(&mut self, client_win: u32, req: Request) -> Result<(), ServerError> {
366        send_req_impl(
367            &self.has_conn,
368            &self.atoms,
369            client_win,
370            &mut self.buf,
371            &mut self.sequence,
372            20,
373            &req,
374        )
375    }
376
377    #[inline]
378    fn deserialize_event(&self, ev: &xim_parser::XEvent) -> Self::XEvent {
379        deserialize_event_impl(ev)
380    }
381}
382
383#[cfg(feature = "x11rb-client")]
384pub struct X11rbClient<C: HasConnection> {
385    has_conn: C,
386    server_owner_window: Window,
387    im_window: Window,
388    server_atom: Atom,
389    atoms: Atoms<Atom>,
390    transport_max: usize,
391    client_window: u32,
392    im_attributes: AHashMap<AttributeName, u16>,
393    ic_attributes: AHashMap<AttributeName, u16>,
394    sequence: u16,
395    buf: Vec<u8>,
396}
397
398#[cfg(feature = "x11rb-client")]
399impl<C: HasConnection> X11rbClient<C> {
400    pub fn init(
401        has_conn: C,
402        screen_num: usize,
403        im_name: Option<&str>,
404    ) -> Result<Self, ClientError> {
405        let conn = has_conn.conn();
406        let screen = &conn.setup().roots[screen_num];
407        let client_window = conn.generate_id()?;
408
409        conn.create_window(
410            COPY_DEPTH_FROM_PARENT,
411            client_window,
412            screen.root,
413            0,
414            0,
415            1,
416            1,
417            0,
418            WindowClass::INPUT_ONLY,
419            screen.root_visual,
420            &Default::default(),
421        )?;
422
423        let var = std::env::var("XMODIFIERS").ok();
424        let var = var.as_ref().and_then(|n| n.strip_prefix("@im="));
425        let im_name = im_name.or(var).ok_or(ClientError::NoXimServer)?;
426
427        log::info!("Try connect {}", im_name);
428
429        let atoms = Atoms::new::<ClientError, _>(|name| {
430            Ok(conn.intern_atom(false, name.as_bytes())?.reply()?.atom)
431        })?;
432        let server_reply = conn
433            .get_property(
434                false,
435                screen.root,
436                atoms.XIM_SERVERS,
437                AtomEnum::ATOM,
438                0,
439                u32::MAX,
440            )?
441            .reply()?;
442
443        if server_reply.type_ != u32::from(AtomEnum::ATOM) || server_reply.format != 32 {
444            Err(ClientError::InvalidReply)
445        } else {
446            for server_atom in server_reply.value32().ok_or(ClientError::InvalidReply)? {
447                let server_owner = conn.get_selection_owner(server_atom)?.reply()?.owner;
448                let name = conn.get_atom_name(server_atom)?.reply()?.name;
449
450                let name = match String::from_utf8(name) {
451                    Ok(name) => name,
452                    _ => continue,
453                };
454
455                if let Some(name) = name.strip_prefix("@server=") {
456                    if name == im_name {
457                        conn.convert_selection(
458                            client_window,
459                            server_atom,
460                            atoms.TRANSPORT,
461                            atoms.TRANSPORT,
462                            CURRENT_TIME,
463                        )?;
464
465                        conn.flush()?;
466
467                        return Ok(Self {
468                            has_conn,
469                            atoms,
470                            server_atom,
471                            server_owner_window: server_owner,
472                            im_attributes: AHashMap::with_hasher(Default::default()),
473                            ic_attributes: AHashMap::with_hasher(Default::default()),
474                            im_window: x11rb::NONE,
475                            transport_max: 20,
476                            client_window,
477                            sequence: 0,
478                            buf: Vec::with_capacity(1024),
479                        });
480                    }
481                }
482            }
483
484            Err(ClientError::NoXimServer)
485        }
486    }
487
488    pub fn filter_event(
489        &mut self,
490        e: &Event,
491        handler: &mut impl ClientHandler<Self>,
492    ) -> Result<bool, ClientError> {
493        match e {
494            Event::SelectionNotify(e) if e.requestor == self.client_window => {
495                if e.property == self.atoms.LOCALES {
496                    // TODO: set locale
497                    let _locale = self
498                        .conn()
499                        .get_property(
500                            true,
501                            self.client_window,
502                            self.atoms.LOCALES,
503                            self.atoms.LOCALES,
504                            0,
505                            u32::MAX,
506                        )?
507                        .reply()?;
508
509                    self.xconnect()?;
510
511                    Ok(true)
512                } else if e.property == self.atoms.TRANSPORT {
513                    let transport = self
514                        .conn()
515                        .get_property(
516                            true,
517                            self.client_window,
518                            self.atoms.TRANSPORT,
519                            self.atoms.TRANSPORT,
520                            0,
521                            u32::MAX,
522                        )?
523                        .reply()?;
524
525                    if !transport.value.starts_with(b"@transport=X/") {
526                        return Err(ClientError::UnsupportedTransport);
527                    }
528
529                    self.conn().convert_selection(
530                        self.client_window,
531                        self.server_atom,
532                        self.atoms.LOCALES,
533                        self.atoms.LOCALES,
534                        CURRENT_TIME,
535                    )?;
536
537                    self.conn().flush()?;
538
539                    Ok(true)
540                } else {
541                    Ok(false)
542                }
543            }
544            Event::ClientMessage(msg) if msg.window == self.client_window => {
545                if msg.type_ == self.atoms.XIM_XCONNECT {
546                    let [im_window, major, minor, max, _] = msg.data.as_data32();
547                    log::info!(
548                        "XConnected server on {}, transport version: {}.{}, TRANSPORT_MAX: {}",
549                        im_window,
550                        major,
551                        minor,
552                        max
553                    );
554                    self.im_window = im_window;
555                    self.transport_max = max as usize;
556                    self.send_req(Request::Connect {
557                        client_major_protocol_version: 1,
558                        client_minor_protocol_version: 0,
559                        endian: xim_parser::Endian::Native,
560                        client_auth_protocol_names: Vec::new(),
561                    })?;
562                    Ok(true)
563                } else if msg.type_ == self.atoms.XIM_PROTOCOL {
564                    self.handle_xim_protocol(msg, handler)?;
565                    Ok(true)
566                } else {
567                    Ok(false)
568                }
569            }
570            Event::Error(ref err)
571                if err.error_kind == x11rb::protocol::ErrorKind::Window
572                    && err.bad_value == self.im_window =>
573            {
574                Err(ClientError::NoXimServer)
575            }
576            _ => Ok(false),
577        }
578    }
579
580    fn handle_xim_protocol(
581        &mut self,
582        msg: &ClientMessageEvent,
583        handler: &mut impl ClientHandler<Self>,
584    ) -> Result<(), ClientError> {
585        if msg.format == 32 {
586            let [length, atom, ..] = msg.data.as_data32();
587            let reply = self
588                .conn()
589                .get_property(true, msg.window, atom, AtomEnum::ANY, 0, length)?
590                .reply()?;
591            // handle fcitx4 occasionally sending empty reply
592            if reply.value_len == 0 {
593                return Err(ClientError::InvalidReply);
594            }
595            let data = reply.value;
596            let req = xim_parser::read(&data)?;
597            client_handle_request(self, handler, req)?;
598        } else if msg.format == 8 {
599            let data = msg.data.as_data8();
600            let req: xim_parser::Request = xim_parser::read(&data)?;
601            client_handle_request(self, handler, req)?;
602        }
603
604        Ok(())
605    }
606
607    fn xconnect(&mut self) -> Result<(), ClientError> {
608        self.conn().send_event(
609            false,
610            self.server_owner_window,
611            EventMask::NO_EVENT,
612            ClientMessageEvent {
613                data: [self.client_window, 0, 0, 0, 0].into(),
614                format: 32,
615                response_type: CLIENT_MESSAGE_EVENT,
616                sequence: 0,
617                type_: self.atoms.XIM_XCONNECT,
618                window: self.server_owner_window,
619            },
620        )?;
621
622        self.conn().flush()?;
623
624        Ok(())
625    }
626}
627
628#[cfg(feature = "x11rb-client")]
629impl<C: HasConnection> ClientCore for X11rbClient<C> {
630    type XEvent = KeyPressEvent;
631    fn set_attrs(&mut self, im_attrs: Vec<Attr>, ic_attrs: Vec<Attr>) {
632        for im_attr in im_attrs {
633            self.im_attributes.insert(im_attr.name, im_attr.id);
634        }
635
636        for ic_attr in ic_attrs {
637            self.ic_attributes.insert(ic_attr.name, ic_attr.id);
638        }
639    }
640
641    #[inline]
642    fn ic_attributes(&self) -> &AHashMap<AttributeName, u16> {
643        &self.ic_attributes
644    }
645
646    #[inline]
647    fn im_attributes(&self) -> &AHashMap<AttributeName, u16> {
648        &self.im_attributes
649    }
650
651    #[inline]
652    fn serialize_event(&self, xev: &Self::XEvent) -> xim_parser::XEvent {
653        xim_parser::XEvent {
654            response_type: xev.response_type,
655            detail: xev.detail,
656            sequence: xev.sequence,
657            time: xev.time,
658            root: xev.root,
659            event: xev.event,
660            child: xev.child,
661            root_x: xev.root_x,
662            root_y: xev.root_y,
663            event_x: xev.event_x,
664            event_y: xev.event_y,
665            state: xev.state.into(),
666            same_screen: xev.same_screen,
667        }
668    }
669
670    #[inline]
671    fn deserialize_event(&self, xev: &xim_parser::XEvent) -> Self::XEvent {
672        deserialize_event_impl(xev)
673    }
674
675    #[inline]
676    fn send_req(&mut self, req: Request) -> Result<(), ClientError> {
677        send_req_impl(
678            &self.has_conn,
679            &self.atoms,
680            self.im_window,
681            &mut self.buf,
682            &mut self.sequence,
683            self.transport_max,
684            &req,
685        )
686    }
687}
688
689fn send_req_impl<C: HasConnection, E: From<ConnectionError> + From<ReplyError>>(
690    c: &C,
691    atoms: &Atoms<Atom>,
692    target: Window,
693    buf: &mut Vec<u8>,
694    sequence: &mut u16,
695    transport_max: usize,
696    req: &Request,
697) -> Result<(), E> {
698    if log::log_enabled!(log::Level::Trace) {
699        log::trace!("->: {:?}", req);
700    } else {
701        log::debug!("->: {}", req.name());
702    }
703    buf.resize(req.size(), 0);
704    xim_parser::write(req, buf);
705
706    if buf.len() < transport_max {
707        if buf.len() > 20 {
708            todo!("multi-CM");
709        }
710        buf.resize(20, 0);
711        let buf: [u8; 20] = buf.as_slice().try_into().unwrap();
712        c.conn().send_event(
713            false,
714            target,
715            EventMask::NO_EVENT,
716            ClientMessageEvent {
717                response_type: CLIENT_MESSAGE_EVENT,
718                data: buf.into(),
719                format: 8,
720                sequence: 0,
721                type_: atoms.XIM_PROTOCOL,
722                window: target,
723            },
724        )?;
725    } else {
726        let prop = c
727            .conn()
728            .intern_atom(false, format!("_XIM_DATA_{}", sequence).as_bytes())?
729            .reply()?
730            .atom;
731        *sequence = sequence.wrapping_add(1);
732        c.conn().change_property(
733            PropMode::APPEND,
734            target,
735            prop,
736            AtomEnum::STRING,
737            8,
738            buf.len() as u32,
739            buf,
740        )?;
741        c.conn().send_event(
742            false,
743            target,
744            EventMask::NO_EVENT,
745            ClientMessageEvent {
746                data: [buf.len() as u32, prop, 0, 0, 0].into(),
747                format: 32,
748                sequence: 0,
749                response_type: CLIENT_MESSAGE_EVENT,
750                type_: atoms.XIM_PROTOCOL,
751                window: target,
752            },
753        )?;
754    }
755    buf.clear();
756    c.conn().flush()?;
757    Ok(())
758}
759
760#[inline]
761fn deserialize_event_impl(xev: &xim_parser::XEvent) -> KeyPressEvent {
762    KeyPressEvent {
763        response_type: xev.response_type,
764        detail: xev.detail,
765        sequence: xev.sequence,
766        time: xev.time,
767        root: xev.root,
768        event: xev.event,
769        child: xev.child,
770        root_x: xev.root_x,
771        root_y: xev.root_y,
772        event_x: xev.event_x,
773        event_y: xev.event_y,
774        state: xev.state.into(),
775        same_screen: xev.same_screen,
776    }
777}