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