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