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