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