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