client.rs

  1use std::cell::RefCell;
  2use std::ops::Deref;
  3use std::rc::{Rc, Weak};
  4use std::time::{Duration, Instant};
  5
  6use calloop::generic::{FdWrapper, Generic};
  7use calloop::{channel, EventLoop, LoopHandle, RegistrationToken};
  8
  9use collections::HashMap;
 10use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
 11use copypasta::ClipboardProvider;
 12
 13use util::ResultExt;
 14use x11rb::connection::{Connection, RequestConnection};
 15use x11rb::cursor;
 16use x11rb::errors::ConnectionError;
 17use x11rb::protocol::randr::ConnectionExt as _;
 18use x11rb::protocol::xinput::{ConnectionExt, ScrollClass};
 19use x11rb::protocol::xkb::ConnectionExt as _;
 20use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
 21use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
 22use x11rb::resource_manager::Database;
 23use x11rb::xcb_ffi::XCBConnection;
 24use xim::{x11rb::X11rbClient, Client};
 25use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
 26use xkbcommon::xkb as xkbc;
 27
 28use crate::platform::linux::LinuxClient;
 29use crate::platform::{LinuxCommon, PlatformWindow};
 30use crate::{
 31    modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
 32    Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
 33    Size, TouchPhase, WindowParams, X11Window,
 34};
 35
 36use super::{
 37    super::{open_uri_internal, SCROLL_LINES},
 38    X11Display, X11WindowStatePtr, XcbAtoms,
 39};
 40use super::{button_from_mask, button_of_key, modifiers_from_state};
 41use super::{XimCallbackEvent, XimHandler};
 42use crate::platform::linux::is_within_click_distance;
 43use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
 44
 45pub(crate) struct WindowRef {
 46    window: X11WindowStatePtr,
 47    refresh_event_token: RegistrationToken,
 48}
 49
 50impl Deref for WindowRef {
 51    type Target = X11WindowStatePtr;
 52
 53    fn deref(&self) -> &Self::Target {
 54        &self.window
 55    }
 56}
 57
 58#[derive(Debug)]
 59#[non_exhaustive]
 60pub enum EventHandlerError {
 61    XCBConnectionError(ConnectionError),
 62    XIMClientError(xim::ClientError),
 63}
 64
 65impl std::error::Error for EventHandlerError {}
 66
 67impl std::fmt::Display for EventHandlerError {
 68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 69        match self {
 70            EventHandlerError::XCBConnectionError(err) => err.fmt(f),
 71            EventHandlerError::XIMClientError(err) => err.fmt(f),
 72        }
 73    }
 74}
 75
 76impl From<ConnectionError> for EventHandlerError {
 77    fn from(err: ConnectionError) -> Self {
 78        EventHandlerError::XCBConnectionError(err)
 79    }
 80}
 81
 82impl From<xim::ClientError> for EventHandlerError {
 83    fn from(err: xim::ClientError) -> Self {
 84        EventHandlerError::XIMClientError(err)
 85    }
 86}
 87
 88pub struct X11ClientState {
 89    pub(crate) loop_handle: LoopHandle<'static, X11Client>,
 90    pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
 91
 92    pub(crate) last_click: Instant,
 93    pub(crate) last_location: Point<Pixels>,
 94    pub(crate) current_count: usize,
 95
 96    pub(crate) scale_factor: f32,
 97
 98    pub(crate) xcb_connection: Rc<XCBConnection>,
 99    pub(crate) x_root_index: usize,
100    pub(crate) resource_database: Database,
101    pub(crate) atoms: XcbAtoms,
102    pub(crate) windows: HashMap<xproto::Window, WindowRef>,
103    pub(crate) focused_window: Option<xproto::Window>,
104    pub(crate) xkb: xkbc::State,
105    pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
106    pub(crate) xim_handler: Option<XimHandler>,
107
108    pub(crate) cursor_handle: cursor::Handle,
109    pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
110    pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
111
112    pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
113    pub(crate) scroll_x: Option<f32>,
114    pub(crate) scroll_y: Option<f32>,
115
116    pub(crate) common: LinuxCommon,
117    pub(crate) clipboard: X11ClipboardContext<Clipboard>,
118    pub(crate) primary: X11ClipboardContext<Primary>,
119}
120
121#[derive(Clone)]
122pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>);
123
124impl X11ClientStatePtr {
125    pub fn drop_window(&self, x_window: u32) {
126        let client = X11Client(self.0.upgrade().expect("client already dropped"));
127        let mut state = client.0.borrow_mut();
128
129        if let Some(window_ref) = state.windows.remove(&x_window) {
130            state.loop_handle.remove(window_ref.refresh_event_token);
131        }
132
133        state.cursor_styles.remove(&x_window);
134
135        if state.windows.is_empty() {
136            state.common.signal.stop();
137        }
138    }
139}
140
141#[derive(Clone)]
142pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
143
144impl X11Client {
145    pub(crate) fn new() -> Self {
146        let event_loop = EventLoop::try_new().unwrap();
147
148        let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
149
150        let handle = event_loop.handle();
151
152        handle.insert_source(main_receiver, |event, _, _: &mut X11Client| {
153            if let calloop::channel::Event::Msg(runnable) = event {
154                runnable.run();
155            }
156        });
157
158        let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
159        xcb_connection
160            .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
161            .unwrap();
162        xcb_connection
163            .prefetch_extension_information(randr::X11_EXTENSION_NAME)
164            .unwrap();
165        xcb_connection
166            .prefetch_extension_information(render::X11_EXTENSION_NAME)
167            .unwrap();
168        xcb_connection
169            .prefetch_extension_information(xinput::X11_EXTENSION_NAME)
170            .unwrap();
171
172        let xinput_version = xcb_connection
173            .xinput_xi_query_version(2, 0)
174            .unwrap()
175            .reply()
176            .unwrap();
177        assert!(
178            xinput_version.major_version >= 2,
179            "XInput Extension v2 not supported."
180        );
181
182        let master_device_query = xcb_connection
183            .xinput_xi_query_device(1_u16)
184            .unwrap()
185            .reply()
186            .unwrap();
187        let scroll_class_data = master_device_query
188            .infos
189            .iter()
190            .find(|info| info.type_ == xinput::DeviceType::MASTER_POINTER)
191            .unwrap()
192            .classes
193            .iter()
194            .filter_map(|class| class.data.as_scroll())
195            .map(|class| *class)
196            .collect::<Vec<_>>();
197
198        let atoms = XcbAtoms::new(&xcb_connection).unwrap();
199        let xkb = xcb_connection
200            .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
201            .unwrap();
202
203        let atoms = atoms.reply().unwrap();
204        let xkb = xkb.reply().unwrap();
205        let events = xkb::EventType::STATE_NOTIFY;
206        xcb_connection
207            .xkb_select_events(
208                xkb::ID::USE_CORE_KBD.into(),
209                0u8.into(),
210                events,
211                0u8.into(),
212                0u8.into(),
213                &xkb::SelectEventsAux::new(),
214            )
215            .unwrap();
216        assert!(xkb.supported);
217
218        let xkb_state = {
219            let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
220            let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
221            let xkb_keymap = xkbc::x11::keymap_new_from_device(
222                &xkb_context,
223                &xcb_connection,
224                xkb_device_id,
225                xkbc::KEYMAP_COMPILE_NO_FLAGS,
226            );
227            xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
228        };
229
230        let screen = xcb_connection.setup().roots.get(x_root_index).unwrap();
231
232        // Values from `Database::GET_RESOURCE_DATABASE`
233        let resource_manager = xcb_connection
234            .get_property(
235                false,
236                screen.root,
237                xproto::AtomEnum::RESOURCE_MANAGER,
238                xproto::AtomEnum::STRING,
239                0,
240                100_000_000,
241            )
242            .unwrap();
243        let resource_manager = resource_manager.reply().unwrap();
244
245        // todo(linux): read hostname
246        let resource_database = Database::new_from_default(&resource_manager, "HOSTNAME".into());
247
248        let scale_factor = resource_database
249            .get_value("Xft.dpi", "Xft.dpi")
250            .ok()
251            .flatten()
252            .map(|dpi: f32| dpi / 96.0)
253            .unwrap_or(1.0);
254
255        let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
256            .unwrap()
257            .reply()
258            .unwrap();
259
260        let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
261        let primary = X11ClipboardContext::<Primary>::new().unwrap();
262
263        let xcb_connection = Rc::new(xcb_connection);
264
265        let (xim_tx, xim_rx) = channel::channel::<XimCallbackEvent>();
266
267        let ximc = X11rbClient::init(Rc::clone(&xcb_connection), x_root_index, None).ok();
268        let xim_handler = if ximc.is_some() {
269            Some(XimHandler::new(xim_tx))
270        } else {
271            None
272        };
273
274        // Safety: Safe if xcb::Connection always returns a valid fd
275        let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
276
277        handle
278            .insert_source(
279                Generic::new_with_error::<EventHandlerError>(
280                    fd,
281                    calloop::Interest::READ,
282                    calloop::Mode::Level,
283                ),
284                {
285                    let xcb_connection = xcb_connection.clone();
286                    move |_readiness, _, client| {
287                        while let Some(event) = xcb_connection.poll_for_event()? {
288                            let mut state = client.0.borrow_mut();
289                            if state.ximc.is_none() || state.xim_handler.is_none() {
290                                drop(state);
291                                client.handle_event(event);
292                                continue;
293                            }
294                            let mut ximc = state.ximc.take().unwrap();
295                            let mut xim_handler = state.xim_handler.take().unwrap();
296                            let xim_connected = xim_handler.connected;
297                            drop(state);
298                            let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
299                                Ok(handled) => handled,
300                                Err(err) => {
301                                    log::error!("XIMClientError: {}", err);
302                                    false
303                                }
304                            };
305                            let mut state = client.0.borrow_mut();
306                            state.ximc = Some(ximc);
307                            state.xim_handler = Some(xim_handler);
308                            drop(state);
309                            if xim_filtered {
310                                continue;
311                            }
312                            if xim_connected {
313                                client.xim_handle_event(event);
314                            } else {
315                                client.handle_event(event);
316                            }
317                        }
318                        Ok(calloop::PostAction::Continue)
319                    }
320                },
321            )
322            .expect("Failed to initialize x11 event source");
323        handle
324            .insert_source(xim_rx, {
325                move |chan_event, _, client| match chan_event {
326                    channel::Event::Msg(xim_event) => {
327                        match (xim_event) {
328                            XimCallbackEvent::XimXEvent(event) => {
329                                client.handle_event(event);
330                            }
331                            XimCallbackEvent::XimCommitEvent(window, text) => {
332                                client.xim_handle_commit(window, text);
333                            }
334                            XimCallbackEvent::XimPreeditEvent(window, text) => {
335                                client.xim_handle_preedit(window, text);
336                            }
337                        };
338                    }
339                    channel::Event::Closed => {
340                        log::error!("XIM Event Sender dropped")
341                    }
342                }
343            })
344            .expect("Failed to initialize XIM event source");
345        X11Client(Rc::new(RefCell::new(X11ClientState {
346            event_loop: Some(event_loop),
347            loop_handle: handle,
348            common,
349            last_click: Instant::now(),
350            last_location: Point::new(px(0.0), px(0.0)),
351            current_count: 0,
352            scale_factor,
353
354            xcb_connection,
355            x_root_index,
356            resource_database,
357            atoms,
358            windows: HashMap::default(),
359            focused_window: None,
360            xkb: xkb_state,
361            ximc,
362            xim_handler,
363
364            cursor_handle,
365            cursor_styles: HashMap::default(),
366            cursor_cache: HashMap::default(),
367
368            scroll_class_data,
369            scroll_x: None,
370            scroll_y: None,
371
372            clipboard,
373            primary,
374        })))
375    }
376
377    fn get_window(&self, win: xproto::Window) -> Option<X11WindowStatePtr> {
378        let state = self.0.borrow();
379        state
380            .windows
381            .get(&win)
382            .map(|window_reference| window_reference.window.clone())
383    }
384
385    fn handle_event(&self, event: Event) -> Option<()> {
386        match event {
387            Event::ClientMessage(event) => {
388                let window = self.get_window(event.window)?;
389                let [atom, ..] = event.data.as_data32();
390                let mut state = self.0.borrow_mut();
391
392                if atom == state.atoms.WM_DELETE_WINDOW {
393                    // window "x" button clicked by user
394                    if window.should_close() {
395                        let window_ref = state.windows.remove(&event.window)?;
396                        state.loop_handle.remove(window_ref.refresh_event_token);
397                        // Rest of the close logic is handled in drop_window()
398                    }
399                }
400            }
401            Event::ConfigureNotify(event) => {
402                let bounds = Bounds {
403                    origin: Point {
404                        x: event.x.into(),
405                        y: event.y.into(),
406                    },
407                    size: Size {
408                        width: event.width.into(),
409                        height: event.height.into(),
410                    },
411                };
412                let window = self.get_window(event.window)?;
413                window.configure(bounds);
414            }
415            Event::Expose(event) => {
416                let window = self.get_window(event.window)?;
417                window.refresh();
418            }
419            Event::FocusIn(event) => {
420                let window = self.get_window(event.event)?;
421                window.set_focused(true);
422                self.0.borrow_mut().focused_window = Some(event.event);
423            }
424            Event::FocusOut(event) => {
425                let window = self.get_window(event.event)?;
426                window.set_focused(false);
427                self.0.borrow_mut().focused_window = None;
428            }
429            Event::XkbStateNotify(event) => {
430                let mut state = self.0.borrow_mut();
431                state.xkb.update_mask(
432                    event.base_mods.into(),
433                    event.latched_mods.into(),
434                    event.locked_mods.into(),
435                    0,
436                    0,
437                    event.locked_group.into(),
438                );
439                let modifiers = Modifiers::from_xkb(&state.xkb);
440                let focused_window_id = state.focused_window?;
441                drop(state);
442
443                let focused_window = self.get_window(focused_window_id)?;
444                focused_window.handle_input(PlatformInput::ModifiersChanged(
445                    ModifiersChangedEvent { modifiers },
446                ));
447            }
448            Event::KeyPress(event) => {
449                let window = self.get_window(event.event)?;
450                let mut state = self.0.borrow_mut();
451
452                let modifiers = modifiers_from_state(event.state);
453                let keystroke = {
454                    let code = event.detail.into();
455                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
456                    state.xkb.update_key(code, xkbc::KeyDirection::Down);
457                    let keysym = state.xkb.key_get_one_sym(code);
458                    if keysym.is_modifier_key() {
459                        return Some(());
460                    }
461                    keystroke
462                };
463                drop(state);
464                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
465                    keystroke,
466                    is_held: false,
467                }));
468            }
469            Event::KeyRelease(event) => {
470                let window = self.get_window(event.event)?;
471                let mut state = self.0.borrow_mut();
472
473                let modifiers = modifiers_from_state(event.state);
474                let keystroke = {
475                    let code = event.detail.into();
476                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
477                    state.xkb.update_key(code, xkbc::KeyDirection::Up);
478                    let keysym = state.xkb.key_get_one_sym(code);
479                    if keysym.is_modifier_key() {
480                        return Some(());
481                    }
482                    keystroke
483                };
484                drop(state);
485                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
486            }
487            Event::XinputButtonPress(event) => {
488                let window = self.get_window(event.event)?;
489                let mut state = self.0.borrow_mut();
490
491                let modifiers = modifiers_from_xinput_info(event.mods);
492                let position = point(
493                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
494                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
495                );
496                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
497                    let click_elapsed = state.last_click.elapsed();
498
499                    if click_elapsed < DOUBLE_CLICK_INTERVAL
500                        && is_within_click_distance(state.last_location, position)
501                    {
502                        state.current_count += 1;
503                    } else {
504                        state.current_count = 1;
505                    }
506
507                    state.last_click = Instant::now();
508                    state.last_location = position;
509                    let current_count = state.current_count;
510
511                    drop(state);
512                    window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
513                        button,
514                        position,
515                        modifiers,
516                        click_count: current_count,
517                        first_mouse: false,
518                    }));
519                } else {
520                    log::warn!("Unknown button press: {event:?}");
521                }
522            }
523            Event::XinputButtonRelease(event) => {
524                let window = self.get_window(event.event)?;
525                let state = self.0.borrow();
526                let modifiers = modifiers_from_xinput_info(event.mods);
527                let position = point(
528                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
529                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
530                );
531                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
532                    let click_count = state.current_count;
533                    drop(state);
534                    window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
535                        button,
536                        position,
537                        modifiers,
538                        click_count,
539                    }));
540                }
541            }
542            Event::XinputMotion(event) => {
543                let window = self.get_window(event.event)?;
544                let state = self.0.borrow();
545                let pressed_button = button_from_mask(event.button_mask[0]);
546                let position = point(
547                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
548                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
549                );
550                drop(state);
551                let modifiers = modifiers_from_xinput_info(event.mods);
552
553                let axisvalues = event
554                    .axisvalues
555                    .iter()
556                    .map(|axisvalue| fp3232_to_f32(*axisvalue))
557                    .collect::<Vec<_>>();
558
559                if event.valuator_mask[0] & 3 != 0 {
560                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
561                        position,
562                        pressed_button,
563                        modifiers,
564                    }));
565                }
566
567                let mut valuator_idx = 0;
568                let scroll_class_data = self.0.borrow().scroll_class_data.clone();
569                for shift in 0..32 {
570                    if (event.valuator_mask[0] >> shift) & 1 == 0 {
571                        continue;
572                    }
573
574                    for scroll_class in &scroll_class_data {
575                        if scroll_class.scroll_type == xinput::ScrollType::HORIZONTAL
576                            && scroll_class.number == shift
577                        {
578                            let new_scroll = axisvalues[valuator_idx]
579                                / fp3232_to_f32(scroll_class.increment)
580                                * SCROLL_LINES as f32;
581                            let old_scroll = self.0.borrow().scroll_x;
582                            self.0.borrow_mut().scroll_x = Some(new_scroll);
583
584                            if let Some(old_scroll) = old_scroll {
585                                let delta_scroll = old_scroll - new_scroll;
586                                window.handle_input(PlatformInput::ScrollWheel(
587                                    crate::ScrollWheelEvent {
588                                        position,
589                                        delta: ScrollDelta::Lines(Point::new(delta_scroll, 0.0)),
590                                        modifiers,
591                                        touch_phase: TouchPhase::default(),
592                                    },
593                                ));
594                            }
595                        } else if scroll_class.scroll_type == xinput::ScrollType::VERTICAL
596                            && scroll_class.number == shift
597                        {
598                            // the `increment` is the valuator delta equivalent to one positive unit of scrolling. Here that means SCROLL_LINES lines.
599                            let new_scroll = axisvalues[valuator_idx]
600                                / fp3232_to_f32(scroll_class.increment)
601                                * SCROLL_LINES as f32;
602                            let old_scroll = self.0.borrow().scroll_y;
603                            self.0.borrow_mut().scroll_y = Some(new_scroll);
604
605                            if let Some(old_scroll) = old_scroll {
606                                let delta_scroll = old_scroll - new_scroll;
607                                window.handle_input(PlatformInput::ScrollWheel(
608                                    crate::ScrollWheelEvent {
609                                        position,
610                                        delta: ScrollDelta::Lines(Point::new(0.0, delta_scroll)),
611                                        modifiers,
612                                        touch_phase: TouchPhase::default(),
613                                    },
614                                ));
615                            }
616                        }
617                    }
618
619                    valuator_idx += 1;
620                }
621            }
622            Event::XinputLeave(event) => {
623                self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
624                self.0.borrow_mut().scroll_y = None;
625
626                let window = self.get_window(event.event)?;
627                let state = self.0.borrow();
628                let pressed_button = button_from_mask(event.buttons[0]);
629                let position = point(
630                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
631                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
632                );
633                let modifiers = modifiers_from_xinput_info(event.mods);
634                drop(state);
635
636                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
637                    pressed_button,
638                    position,
639                    modifiers,
640                }));
641            }
642            _ => {}
643        };
644
645        Some(())
646    }
647
648    fn xim_handle_event(&self, event: Event) -> Option<()> {
649        match event {
650            Event::KeyPress(event) | Event::KeyRelease(event) => {
651                let mut state = self.0.borrow_mut();
652                let mut ximc = state.ximc.take().unwrap();
653                let mut xim_handler = state.xim_handler.take().unwrap();
654                drop(state);
655                xim_handler.window = event.event;
656                ximc.forward_event(
657                    xim_handler.im_id,
658                    xim_handler.ic_id,
659                    xim::ForwardEventFlag::empty(),
660                    &event,
661                )
662                .unwrap();
663                let mut state = self.0.borrow_mut();
664                state.ximc = Some(ximc);
665                state.xim_handler = Some(xim_handler);
666                drop(state);
667            }
668            event => {
669                self.handle_event(event);
670            }
671        }
672        Some(())
673    }
674
675    fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
676        let window = self.get_window(window).unwrap();
677
678        window.handle_ime_commit(text);
679        Some(())
680    }
681
682    fn xim_handle_preedit(&self, window: xproto::Window, text: String) -> Option<()> {
683        let window = self.get_window(window).unwrap();
684        window.handle_ime_preedit(text);
685
686        let mut state = self.0.borrow_mut();
687        let mut ximc = state.ximc.take().unwrap();
688        let mut xim_handler = state.xim_handler.take().unwrap();
689        drop(state);
690
691        if let Some(area) = window.get_ime_area() {
692            let ic_attributes = ximc
693                .build_ic_attributes()
694                .push(
695                    xim::AttributeName::InputStyle,
696                    xim::InputStyle::PREEDIT_CALLBACKS
697                        | xim::InputStyle::STATUS_NOTHING
698                        | xim::InputStyle::PREEDIT_POSITION,
699                )
700                .push(xim::AttributeName::ClientWindow, xim_handler.window)
701                .push(xim::AttributeName::FocusWindow, xim_handler.window)
702                .nested_list(xim::AttributeName::PreeditAttributes, |b| {
703                    b.push(
704                        xim::AttributeName::SpotLocation,
705                        xim::Point {
706                            x: u32::from(area.origin.x + area.size.width) as i16,
707                            y: u32::from(area.origin.y + area.size.height) as i16,
708                        },
709                    );
710                })
711                .build();
712            ximc.set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes);
713        }
714        let mut state = self.0.borrow_mut();
715        state.ximc = Some(ximc);
716        state.xim_handler = Some(xim_handler);
717        drop(state);
718        Some(())
719    }
720}
721
722impl LinuxClient for X11Client {
723    fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
724        f(&mut self.0.borrow_mut().common)
725    }
726
727    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
728        let state = self.0.borrow();
729        let setup = state.xcb_connection.setup();
730        setup
731            .roots
732            .iter()
733            .enumerate()
734            .filter_map(|(root_id, _)| {
735                Some(Rc::new(X11Display::new(&state.xcb_connection, root_id)?)
736                    as Rc<dyn PlatformDisplay>)
737            })
738            .collect()
739    }
740
741    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
742        let state = self.0.borrow();
743
744        Some(Rc::new(
745            X11Display::new(&state.xcb_connection, state.x_root_index)
746                .expect("There should always be a root index"),
747        ))
748    }
749
750    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
751        let state = self.0.borrow();
752
753        Some(Rc::new(X11Display::new(
754            &state.xcb_connection,
755            id.0 as usize,
756        )?))
757    }
758
759    fn open_window(
760        &self,
761        _handle: AnyWindowHandle,
762        params: WindowParams,
763    ) -> Box<dyn PlatformWindow> {
764        let mut state = self.0.borrow_mut();
765        let x_window = state.xcb_connection.generate_id().unwrap();
766
767        let window = X11Window::new(
768            X11ClientStatePtr(Rc::downgrade(&self.0)),
769            state.common.foreground_executor.clone(),
770            params,
771            &state.xcb_connection,
772            state.x_root_index,
773            x_window,
774            &state.atoms,
775            state.scale_factor,
776        );
777
778        let screen_resources = state
779            .xcb_connection
780            .randr_get_screen_resources(x_window)
781            .unwrap()
782            .reply()
783            .expect("Could not find available screens");
784
785        let mode = screen_resources
786            .crtcs
787            .iter()
788            .find_map(|crtc| {
789                let crtc_info = state
790                    .xcb_connection
791                    .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
792                    .ok()?
793                    .reply()
794                    .ok()?;
795
796                screen_resources
797                    .modes
798                    .iter()
799                    .find(|m| m.id == crtc_info.mode)
800            })
801            .expect("Unable to find screen refresh rate");
802
803        let refresh_event_token = state
804            .loop_handle
805            .insert_source(calloop::timer::Timer::immediate(), {
806                let refresh_duration = mode_refresh_rate(mode);
807                move |mut instant, (), client| {
808                    let state = client.0.borrow_mut();
809                    state
810                        .xcb_connection
811                        .send_event(
812                            false,
813                            x_window,
814                            xproto::EventMask::EXPOSURE,
815                            xproto::ExposeEvent {
816                                response_type: xproto::EXPOSE_EVENT,
817                                sequence: 0,
818                                window: x_window,
819                                x: 0,
820                                y: 0,
821                                width: 0,
822                                height: 0,
823                                count: 1,
824                            },
825                        )
826                        .unwrap();
827                    let _ = state.xcb_connection.flush().unwrap();
828                    // Take into account that some frames have been skipped
829                    let now = time::Instant::now();
830                    while instant < now {
831                        instant += refresh_duration;
832                    }
833                    calloop::timer::TimeoutAction::ToInstant(instant)
834                }
835            })
836            .expect("Failed to initialize refresh timer");
837
838        let window_ref = WindowRef {
839            window: window.0.clone(),
840            refresh_event_token,
841        };
842
843        state.windows.insert(x_window, window_ref);
844        Box::new(window)
845    }
846
847    fn set_cursor_style(&self, style: CursorStyle) {
848        let mut state = self.0.borrow_mut();
849        let Some(focused_window) = state.focused_window else {
850            return;
851        };
852        let current_style = state
853            .cursor_styles
854            .get(&focused_window)
855            .unwrap_or(&CursorStyle::Arrow);
856        if *current_style == style {
857            return;
858        }
859
860        let cursor = match state.cursor_cache.get(&style) {
861            Some(cursor) => *cursor,
862            None => {
863                let cursor = state
864                    .cursor_handle
865                    .load_cursor(&state.xcb_connection, &style.to_icon_name())
866                    .expect("failed to load cursor");
867                state.cursor_cache.insert(style, cursor);
868                cursor
869            }
870        };
871
872        state.cursor_styles.insert(focused_window, style);
873        state
874            .xcb_connection
875            .change_window_attributes(
876                focused_window,
877                &ChangeWindowAttributesAux {
878                    cursor: Some(cursor),
879                    ..Default::default()
880                },
881            )
882            .expect("failed to change window cursor");
883    }
884
885    fn open_uri(&self, uri: &str) {
886        open_uri_internal(uri, None);
887    }
888
889    fn write_to_primary(&self, item: crate::ClipboardItem) {
890        self.0.borrow_mut().primary.set_contents(item.text);
891    }
892
893    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
894        self.0.borrow_mut().clipboard.set_contents(item.text);
895    }
896
897    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
898        self.0
899            .borrow_mut()
900            .primary
901            .get_contents()
902            .ok()
903            .map(|text| crate::ClipboardItem {
904                text,
905                metadata: None,
906            })
907    }
908
909    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
910        self.0
911            .borrow_mut()
912            .clipboard
913            .get_contents()
914            .ok()
915            .map(|text| crate::ClipboardItem {
916                text,
917                metadata: None,
918            })
919    }
920
921    fn run(&self) {
922        let mut event_loop = self
923            .0
924            .borrow_mut()
925            .event_loop
926            .take()
927            .expect("App is already running");
928
929        event_loop.run(None, &mut self.clone(), |_| {}).log_err();
930    }
931}
932
933// Adatpted from:
934// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
935pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
936    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
937    let micros = 1_000_000_000 / millihertz;
938    log::info!("Refreshing at {} micros", micros);
939    Duration::from_micros(micros)
940}
941
942fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
943    value.integral as f32 + value.frac as f32 / u32::MAX as f32
944}