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