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