1use std::cell::{RefCell, RefMut};
2use std::rc::{Rc, Weak};
3use std::time::{Duration, Instant};
4
5use async_task::Runnable;
6use calloop::timer::{TimeoutAction, Timer};
7use calloop::{EventLoop, LoopHandle};
8use calloop_wayland_source::WaylandSource;
9use collections::HashMap;
10use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
11use copypasta::ClipboardProvider;
12use util::ResultExt;
13use wayland_backend::client::ObjectId;
14use wayland_backend::protocol::WEnum;
15use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};
16use wayland_client::protocol::wl_callback::{self, WlCallback};
17use wayland_client::protocol::wl_output;
18use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
19use wayland_client::{
20 delegate_noop,
21 protocol::{
22 wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,
23 wl_shm_pool, wl_surface,
24 },
25 Connection, Dispatch, Proxy, QueueHandle,
26};
27use wayland_protocols::wp::fractional_scale::v1::client::{
28 wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
29};
30use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
31use wayland_protocols::xdg::decoration::zv1::client::{
32 zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
33};
34use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
35use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
36use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
37
38use super::super::DOUBLE_CLICK_INTERVAL;
39use super::window::{WaylandWindowState, WaylandWindowStatePtr};
40use crate::platform::linux::is_within_click_distance;
41use crate::platform::linux::wayland::cursor::Cursor;
42use crate::platform::linux::wayland::window::WaylandWindow;
43use crate::platform::linux::LinuxClient;
44use crate::platform::PlatformWindow;
45use crate::{point, px, ForegroundExecutor, MouseExitEvent};
46use crate::{
47 AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
48 ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
49 NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
50 ScrollWheelEvent, TouchPhase,
51};
52use crate::{LinuxCommon, WindowParams};
53
54/// Used to convert evdev scancode to xkb scancode
55const MIN_KEYCODE: u32 = 8;
56
57#[derive(Clone)]
58pub struct Globals {
59 pub qh: QueueHandle<WaylandClientStatePtr>,
60 pub compositor: wl_compositor::WlCompositor,
61 pub wm_base: xdg_wm_base::XdgWmBase,
62 pub shm: wl_shm::WlShm,
63 pub viewporter: Option<wp_viewporter::WpViewporter>,
64 pub fractional_scale_manager:
65 Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
66 pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
67 pub executor: ForegroundExecutor,
68}
69
70impl Globals {
71 fn new(
72 globals: GlobalList,
73 executor: ForegroundExecutor,
74 qh: QueueHandle<WaylandClientStatePtr>,
75 ) -> Self {
76 Globals {
77 compositor: globals
78 .bind(
79 &qh,
80 wl_surface::REQ_SET_BUFFER_SCALE_SINCE
81 ..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE,
82 (),
83 )
84 .unwrap(),
85 shm: globals.bind(&qh, 1..=1, ()).unwrap(),
86 wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
87 viewporter: globals.bind(&qh, 1..=1, ()).ok(),
88 fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
89 decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
90 executor,
91 qh,
92 }
93 }
94}
95
96pub(crate) struct WaylandClientState {
97 globals: Globals,
98 wl_pointer: Option<wl_pointer::WlPointer>,
99 // Surface to Window mapping
100 windows: HashMap<ObjectId, WaylandWindowStatePtr>,
101 // Output to scale mapping
102 output_scales: HashMap<ObjectId, i32>,
103 keymap_state: Option<xkb::State>,
104 click: ClickState,
105 repeat: KeyRepeat,
106 modifiers: Modifiers,
107 axis_source: AxisSource,
108 mouse_location: Option<Point<Pixels>>,
109 continuous_scroll_delta: Option<Point<Pixels>>,
110 discrete_scroll_delta: Option<Point<f32>>,
111 vertical_modifier: f32,
112 horizontal_modifier: f32,
113 scroll_event_received: bool,
114 enter_token: Option<()>,
115 button_pressed: Option<MouseButton>,
116 mouse_focused_window: Option<WaylandWindowStatePtr>,
117 keyboard_focused_window: Option<WaylandWindowStatePtr>,
118 loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
119 cursor_icon_name: String,
120 cursor: Cursor,
121 clipboard: Option<Clipboard>,
122 primary: Option<Primary>,
123 event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
124 common: LinuxCommon,
125}
126
127pub struct ClickState {
128 last_click: Instant,
129 last_location: Point<Pixels>,
130 current_count: usize,
131}
132
133pub(crate) struct KeyRepeat {
134 characters_per_second: u32,
135 delay: Duration,
136 current_id: u64,
137 current_keysym: Option<xkb::Keysym>,
138}
139
140/// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the
141/// window to GPUI.
142#[derive(Clone)]
143pub struct WaylandClientStatePtr(Weak<RefCell<WaylandClientState>>);
144
145impl WaylandClientStatePtr {
146 fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {
147 self.0
148 .upgrade()
149 .expect("The pointer should always be valid when dispatching in wayland")
150 }
151
152 pub fn drop_window(&self, surface_id: &ObjectId) {
153 let mut client = self.get_client();
154 let mut state = client.borrow_mut();
155 let closed_window = state.windows.remove(surface_id).unwrap();
156 if let Some(window) = state.mouse_focused_window.take() {
157 if !window.ptr_eq(&closed_window) {
158 state.mouse_focused_window = Some(window);
159 }
160 }
161 if let Some(window) = state.keyboard_focused_window.take() {
162 if !window.ptr_eq(&closed_window) {
163 state.mouse_focused_window = Some(window);
164 }
165 }
166 if state.windows.is_empty() {
167 // Drop the clipboard to prevent a seg fault after we've closed all Wayland connections.
168 state.clipboard = None;
169 state.primary = None;
170 state.common.signal.stop();
171 }
172 }
173}
174
175#[derive(Clone)]
176pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
177
178const WL_OUTPUT_VERSION: u32 = 2;
179
180fn wl_seat_version(version: u32) -> u32 {
181 // We rely on the wl_pointer.frame event
182 const WL_SEAT_MIN_VERSION: u32 = 5;
183 const WL_SEAT_MAX_VERSION: u32 = 9;
184
185 if version < WL_SEAT_MIN_VERSION {
186 panic!(
187 "wl_seat below required version: {} < {}",
188 version, WL_SEAT_MIN_VERSION
189 );
190 }
191
192 version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)
193}
194
195impl WaylandClient {
196 pub(crate) fn new() -> Self {
197 let conn = Connection::connect_to_env().unwrap();
198
199 let (globals, mut event_queue) =
200 registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
201 let qh = event_queue.handle();
202 let mut outputs = HashMap::default();
203
204 globals.contents().with_list(|list| {
205 for global in list {
206 match &global.interface[..] {
207 "wl_seat" => {
208 globals.registry().bind::<wl_seat::WlSeat, _, _>(
209 global.name,
210 wl_seat_version(global.version),
211 &qh,
212 (),
213 );
214 }
215 "wl_output" => {
216 let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
217 global.name,
218 WL_OUTPUT_VERSION,
219 &qh,
220 (),
221 );
222 outputs.insert(output.id(), 1);
223 }
224 _ => {}
225 }
226 }
227 });
228
229 let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
230 let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
231
232 let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
233
234 let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
235
236 let handle = event_loop.handle();
237
238 handle.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
239 if let calloop::channel::Event::Msg(runnable) = event {
240 runnable.run();
241 }
242 });
243
244 let globals = Globals::new(globals, common.foreground_executor.clone(), qh);
245
246 let cursor = Cursor::new(&conn, &globals, 24);
247
248 let mut state = Rc::new(RefCell::new(WaylandClientState {
249 globals,
250 wl_pointer: None,
251 output_scales: outputs,
252 windows: HashMap::default(),
253 common,
254 keymap_state: None,
255 click: ClickState {
256 last_click: Instant::now(),
257 last_location: Point::new(px(0.0), px(0.0)),
258 current_count: 0,
259 },
260 repeat: KeyRepeat {
261 characters_per_second: 16,
262 delay: Duration::from_millis(500),
263 current_id: 0,
264 current_keysym: None,
265 },
266 modifiers: Modifiers {
267 shift: false,
268 control: false,
269 alt: false,
270 function: false,
271 platform: false,
272 },
273 scroll_event_received: false,
274 axis_source: AxisSource::Wheel,
275 mouse_location: None,
276 continuous_scroll_delta: None,
277 discrete_scroll_delta: None,
278 vertical_modifier: -1.0,
279 horizontal_modifier: -1.0,
280 button_pressed: None,
281 mouse_focused_window: None,
282 keyboard_focused_window: None,
283 loop_handle: handle.clone(),
284 cursor_icon_name: "arrow".to_string(),
285 enter_token: None,
286 cursor,
287 clipboard: Some(clipboard),
288 primary: Some(primary),
289 event_loop: Some(event_loop),
290 }));
291
292 WaylandSource::new(conn, event_queue).insert(handle);
293
294 Self(state)
295 }
296}
297
298impl LinuxClient for WaylandClient {
299 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
300 Vec::new()
301 }
302
303 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
304 unimplemented!()
305 }
306
307 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
308 None
309 }
310
311 fn open_window(
312 &self,
313 handle: AnyWindowHandle,
314 params: WindowParams,
315 ) -> Box<dyn PlatformWindow> {
316 let mut state = self.0.borrow_mut();
317
318 let (window, surface_id) = WaylandWindow::new(
319 state.globals.clone(),
320 WaylandClientStatePtr(Rc::downgrade(&self.0)),
321 params,
322 );
323 state.windows.insert(surface_id, window.0.clone());
324
325 Box::new(window)
326 }
327
328 fn set_cursor_style(&self, style: CursorStyle) {
329 // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
330 // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
331 // Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values
332 let cursor_icon_name = match style {
333 CursorStyle::Arrow => "arrow",
334 CursorStyle::IBeam => "text",
335 CursorStyle::Crosshair => "crosshair",
336 CursorStyle::ClosedHand => "grabbing",
337 CursorStyle::OpenHand => "grab",
338 CursorStyle::PointingHand => "pointer",
339 CursorStyle::ResizeLeft => "w-resize",
340 CursorStyle::ResizeRight => "e-resize",
341 CursorStyle::ResizeLeftRight => "ew-resize",
342 CursorStyle::ResizeUp => "n-resize",
343 CursorStyle::ResizeDown => "s-resize",
344 CursorStyle::ResizeUpDown => "ns-resize",
345 CursorStyle::DisappearingItem => "grabbing", // todo(linux) - couldn't find equivalent icon in linux
346 CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
347 CursorStyle::OperationNotAllowed => "not-allowed",
348 CursorStyle::DragLink => "alias",
349 CursorStyle::DragCopy => "copy",
350 CursorStyle::ContextualMenu => "context-menu",
351 }
352 .to_string();
353
354 let mut state = self.0.borrow_mut();
355 state.cursor_icon_name = cursor_icon_name.clone();
356 if state.mouse_focused_window.is_some() {
357 let wl_pointer = state
358 .wl_pointer
359 .clone()
360 .expect("window is focused by pointer");
361 state.cursor.set_icon(&wl_pointer, &cursor_icon_name);
362 }
363 }
364
365 fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
366 f(&mut self.0.borrow_mut().common)
367 }
368
369 fn run(&self) {
370 let mut event_loop = self
371 .0
372 .borrow_mut()
373 .event_loop
374 .take()
375 .expect("App is already running");
376
377 event_loop
378 .run(
379 None,
380 &mut WaylandClientStatePtr(Rc::downgrade(&self.0)),
381 |_| {},
382 )
383 .log_err();
384 }
385
386 fn write_to_primary(&self, item: crate::ClipboardItem) {
387 self.0
388 .borrow_mut()
389 .primary
390 .as_mut()
391 .unwrap()
392 .set_contents(item.text);
393 }
394
395 fn write_to_clipboard(&self, item: crate::ClipboardItem) {
396 self.0
397 .borrow_mut()
398 .clipboard
399 .as_mut()
400 .unwrap()
401 .set_contents(item.text);
402 }
403
404 fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
405 self.0
406 .borrow_mut()
407 .primary
408 .as_mut()
409 .unwrap()
410 .get_contents()
411 .ok()
412 .map(|s| crate::ClipboardItem {
413 text: s,
414 metadata: None,
415 })
416 }
417
418 fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
419 self.0
420 .borrow_mut()
421 .clipboard
422 .as_mut()
423 .unwrap()
424 .get_contents()
425 .ok()
426 .map(|s| crate::ClipboardItem {
427 text: s,
428 metadata: None,
429 })
430 }
431}
432
433impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
434 fn event(
435 this: &mut Self,
436 registry: &wl_registry::WlRegistry,
437 event: wl_registry::Event,
438 _: &GlobalListContents,
439 _: &Connection,
440 qh: &QueueHandle<Self>,
441 ) {
442 let mut client = this.get_client();
443 let mut state = client.borrow_mut();
444
445 match event {
446 wl_registry::Event::Global {
447 name,
448 interface,
449 version,
450 } => match &interface[..] {
451 "wl_seat" => {
452 state.wl_pointer = None;
453 registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
454 }
455 "wl_output" => {
456 let output =
457 registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ());
458
459 state.output_scales.insert(output.id(), 1);
460 }
461 _ => {}
462 },
463 wl_registry::Event::GlobalRemove { name: _ } => {}
464 _ => {}
465 }
466 }
467}
468
469delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
470delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
471delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
472delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
473delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
474delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
475delegate_noop!(WaylandClientStatePtr: ignore wp_viewporter::WpViewporter);
476delegate_noop!(WaylandClientStatePtr: ignore wp_viewport::WpViewport);
477
478impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
479 fn event(
480 state: &mut WaylandClientStatePtr,
481 _: &wl_callback::WlCallback,
482 event: wl_callback::Event,
483 surface_id: &ObjectId,
484 _: &Connection,
485 qh: &QueueHandle<Self>,
486 ) {
487 let client = state.get_client();
488 let mut state = client.borrow_mut();
489 let Some(window) = get_window(&mut state, surface_id) else {
490 return;
491 };
492 drop(state);
493
494 match event {
495 wl_callback::Event::Done { callback_data } => {
496 window.frame(true);
497 }
498 _ => {}
499 }
500 }
501}
502
503fn get_window(
504 mut state: &mut RefMut<WaylandClientState>,
505 surface_id: &ObjectId,
506) -> Option<WaylandWindowStatePtr> {
507 state.windows.get(surface_id).cloned()
508}
509
510impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
511 fn event(
512 this: &mut Self,
513 surface: &wl_surface::WlSurface,
514 event: <wl_surface::WlSurface as Proxy>::Event,
515 _: &(),
516 _: &Connection,
517 _: &QueueHandle<Self>,
518 ) {
519 let mut client = this.get_client();
520 let mut state = client.borrow_mut();
521
522 let Some(window) = get_window(&mut state, &surface.id()) else {
523 return;
524 };
525 let scales = state.output_scales.clone();
526 drop(state);
527
528 window.handle_surface_event(event, scales);
529 }
530}
531
532impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
533 fn event(
534 this: &mut Self,
535 output: &wl_output::WlOutput,
536 event: <wl_output::WlOutput as Proxy>::Event,
537 _: &(),
538 _: &Connection,
539 _: &QueueHandle<Self>,
540 ) {
541 let mut client = this.get_client();
542 let mut state = client.borrow_mut();
543
544 let Some(mut output_scale) = state.output_scales.get_mut(&output.id()) else {
545 return;
546 };
547
548 match event {
549 wl_output::Event::Scale { factor } => {
550 *output_scale = factor;
551 }
552 _ => {}
553 }
554 }
555}
556
557impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
558 fn event(
559 state: &mut Self,
560 xdg_surface: &xdg_surface::XdgSurface,
561 event: xdg_surface::Event,
562 surface_id: &ObjectId,
563 _: &Connection,
564 _: &QueueHandle<Self>,
565 ) {
566 let client = state.get_client();
567 let mut state = client.borrow_mut();
568 let Some(window) = get_window(&mut state, surface_id) else {
569 return;
570 };
571 drop(state);
572 window.handle_xdg_surface_event(event);
573 }
574}
575
576impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
577 fn event(
578 this: &mut Self,
579 xdg_toplevel: &xdg_toplevel::XdgToplevel,
580 event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
581 surface_id: &ObjectId,
582 _: &Connection,
583 _: &QueueHandle<Self>,
584 ) {
585 let client = this.get_client();
586 let mut state = client.borrow_mut();
587 let Some(window) = get_window(&mut state, surface_id) else {
588 return;
589 };
590
591 drop(state);
592 let should_close = window.handle_toplevel_event(event);
593
594 if should_close {
595 this.drop_window(surface_id);
596 }
597 }
598}
599
600impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
601 fn event(
602 _: &mut Self,
603 wm_base: &xdg_wm_base::XdgWmBase,
604 event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
605 _: &(),
606 _: &Connection,
607 _: &QueueHandle<Self>,
608 ) {
609 if let xdg_wm_base::Event::Ping { serial } = event {
610 wm_base.pong(serial);
611 }
612 }
613}
614
615impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
616 fn event(
617 state: &mut Self,
618 seat: &wl_seat::WlSeat,
619 event: wl_seat::Event,
620 data: &(),
621 conn: &Connection,
622 qh: &QueueHandle<Self>,
623 ) {
624 if let wl_seat::Event::Capabilities {
625 capabilities: WEnum::Value(capabilities),
626 } = event
627 {
628 if capabilities.contains(wl_seat::Capability::Keyboard) {
629 seat.get_keyboard(qh, ());
630 }
631 if capabilities.contains(wl_seat::Capability::Pointer) {
632 let client = state.get_client();
633 let mut state = client.borrow_mut();
634 state.wl_pointer = Some(seat.get_pointer(qh, ()));
635 }
636 }
637 }
638}
639
640impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
641 fn event(
642 this: &mut Self,
643 keyboard: &wl_keyboard::WlKeyboard,
644 event: wl_keyboard::Event,
645 data: &(),
646 conn: &Connection,
647 qh: &QueueHandle<Self>,
648 ) {
649 let mut client = this.get_client();
650 let mut state = client.borrow_mut();
651 match event {
652 wl_keyboard::Event::RepeatInfo { rate, delay } => {
653 state.repeat.characters_per_second = rate as u32;
654 state.repeat.delay = Duration::from_millis(delay as u64);
655 }
656 wl_keyboard::Event::Keymap {
657 format: WEnum::Value(format),
658 fd,
659 size,
660 ..
661 } => {
662 assert_eq!(
663 format,
664 wl_keyboard::KeymapFormat::XkbV1,
665 "Unsupported keymap format"
666 );
667 let keymap = unsafe {
668 xkb::Keymap::new_from_fd(
669 &xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
670 fd,
671 size as usize,
672 XKB_KEYMAP_FORMAT_TEXT_V1,
673 KEYMAP_COMPILE_NO_FLAGS,
674 )
675 .log_err()
676 .flatten()
677 .expect("Failed to create keymap")
678 };
679 state.keymap_state = Some(xkb::State::new(&keymap));
680 }
681 wl_keyboard::Event::Enter { surface, .. } => {
682 state.keyboard_focused_window = get_window(&mut state, &surface.id());
683
684 if let Some(window) = state.keyboard_focused_window.clone() {
685 drop(state);
686 window.set_focused(true);
687 }
688 }
689 wl_keyboard::Event::Leave { surface, .. } => {
690 let keyboard_focused_window = get_window(&mut state, &surface.id());
691 state.keyboard_focused_window = None;
692
693 if let Some(window) = keyboard_focused_window {
694 drop(state);
695 window.set_focused(false);
696 }
697 }
698 wl_keyboard::Event::Modifiers {
699 mods_depressed,
700 mods_latched,
701 mods_locked,
702 group,
703 ..
704 } => {
705 let focused_window = state.keyboard_focused_window.clone();
706 let Some(focused_window) = focused_window else {
707 return;
708 };
709
710 let keymap_state = state.keymap_state.as_mut().unwrap();
711 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
712 state.modifiers = Modifiers::from_xkb(keymap_state);
713
714 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
715 modifiers: state.modifiers,
716 });
717
718 drop(state);
719 focused_window.handle_input(input);
720 }
721 wl_keyboard::Event::Key {
722 key,
723 state: WEnum::Value(key_state),
724 ..
725 } => {
726 let focused_window = state.keyboard_focused_window.clone();
727 let Some(focused_window) = focused_window else {
728 return;
729 };
730 let focused_window = focused_window.clone();
731
732 let keymap_state = state.keymap_state.as_ref().unwrap();
733 let keycode = Keycode::from(key + MIN_KEYCODE);
734 let keysym = keymap_state.key_get_one_sym(keycode);
735
736 match key_state {
737 wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
738 let input = PlatformInput::KeyDown(KeyDownEvent {
739 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
740 is_held: false, // todo(linux)
741 });
742
743 state.repeat.current_id += 1;
744 state.repeat.current_keysym = Some(keysym);
745
746 let rate = state.repeat.characters_per_second;
747 let id = state.repeat.current_id;
748 state
749 .loop_handle
750 .insert_source(Timer::from_duration(state.repeat.delay), {
751 let input = input.clone();
752 move |event, _metadata, this| {
753 let mut client = this.get_client();
754 let mut state = client.borrow_mut();
755 let is_repeating = id == state.repeat.current_id
756 && state.repeat.current_keysym.is_some()
757 && state.keyboard_focused_window.is_some();
758
759 if !is_repeating {
760 return TimeoutAction::Drop;
761 }
762
763 let focused_window =
764 state.keyboard_focused_window.as_ref().unwrap().clone();
765
766 drop(state);
767 focused_window.handle_input(input.clone());
768
769 TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
770 }
771 })
772 .unwrap();
773
774 drop(state);
775 focused_window.handle_input(input);
776 }
777 wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
778 let input = PlatformInput::KeyUp(KeyUpEvent {
779 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
780 });
781
782 state.repeat.current_keysym = None;
783
784 drop(state);
785 focused_window.handle_input(input);
786 }
787 _ => {}
788 }
789 }
790 _ => {}
791 }
792 }
793}
794
795fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
796 // These values are coming from <linux/input-event-codes.h>.
797 const BTN_LEFT: u32 = 0x110;
798 const BTN_RIGHT: u32 = 0x111;
799 const BTN_MIDDLE: u32 = 0x112;
800 const BTN_SIDE: u32 = 0x113;
801 const BTN_EXTRA: u32 = 0x114;
802 const BTN_FORWARD: u32 = 0x115;
803 const BTN_BACK: u32 = 0x116;
804
805 Some(match button {
806 BTN_LEFT => MouseButton::Left,
807 BTN_RIGHT => MouseButton::Right,
808 BTN_MIDDLE => MouseButton::Middle,
809 BTN_BACK | BTN_SIDE => MouseButton::Navigate(NavigationDirection::Back),
810 BTN_FORWARD | BTN_EXTRA => MouseButton::Navigate(NavigationDirection::Forward),
811 _ => return None,
812 })
813}
814
815impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
816 fn event(
817 this: &mut Self,
818 wl_pointer: &wl_pointer::WlPointer,
819 event: wl_pointer::Event,
820 data: &(),
821 conn: &Connection,
822 qh: &QueueHandle<Self>,
823 ) {
824 let mut client = this.get_client();
825 let mut state = client.borrow_mut();
826 let cursor_icon_name = state.cursor_icon_name.clone();
827
828 match event {
829 wl_pointer::Event::Enter {
830 serial,
831 surface,
832 surface_x,
833 surface_y,
834 ..
835 } => {
836 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
837
838 if let Some(window) = get_window(&mut state, &surface.id()) {
839 state.enter_token = Some(());
840 state.mouse_focused_window = Some(window.clone());
841 state.cursor.mark_dirty();
842 state.cursor.set_serial_id(serial);
843 state
844 .cursor
845 .set_icon(&wl_pointer, cursor_icon_name.as_str());
846 drop(state);
847 window.set_focused(true);
848 }
849 }
850 wl_pointer::Event::Leave { surface, .. } => {
851 if let Some(focused_window) = state.mouse_focused_window.clone() {
852 state.enter_token.take();
853 let input = PlatformInput::MouseExited(MouseExitEvent {
854 position: state.mouse_location.unwrap(),
855 pressed_button: state.button_pressed,
856 modifiers: state.modifiers,
857 });
858 state.mouse_focused_window = None;
859 state.mouse_location = None;
860
861 drop(state);
862 focused_window.handle_input(input);
863 focused_window.set_focused(false);
864 }
865 }
866 wl_pointer::Event::Motion {
867 time,
868 surface_x,
869 surface_y,
870 ..
871 } => {
872 if state.mouse_focused_window.is_none() {
873 return;
874 }
875 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
876
877 if let Some(window) = state.mouse_focused_window.clone() {
878 let input = PlatformInput::MouseMove(MouseMoveEvent {
879 position: state.mouse_location.unwrap(),
880 pressed_button: state.button_pressed,
881 modifiers: state.modifiers,
882 });
883 drop(state);
884 window.handle_input(input);
885 }
886 }
887 wl_pointer::Event::Button {
888 button,
889 state: WEnum::Value(button_state),
890 ..
891 } => {
892 let button = linux_button_to_gpui(button);
893 let Some(button) = button else { return };
894 if state.mouse_focused_window.is_none() {
895 return;
896 }
897 match button_state {
898 wl_pointer::ButtonState::Pressed => {
899 let click_elapsed = state.click.last_click.elapsed();
900
901 if click_elapsed < DOUBLE_CLICK_INTERVAL
902 && is_within_click_distance(
903 state.click.last_location,
904 state.mouse_location.unwrap(),
905 )
906 {
907 state.click.current_count += 1;
908 } else {
909 state.click.current_count = 1;
910 }
911
912 state.click.last_click = Instant::now();
913 state.click.last_location = state.mouse_location.unwrap();
914
915 state.button_pressed = Some(button);
916
917 if let Some(window) = state.mouse_focused_window.clone() {
918 let input = PlatformInput::MouseDown(MouseDownEvent {
919 button,
920 position: state.mouse_location.unwrap(),
921 modifiers: state.modifiers,
922 click_count: state.click.current_count,
923 first_mouse: state.enter_token.take().is_some(),
924 });
925 drop(state);
926 window.handle_input(input);
927 }
928 }
929 wl_pointer::ButtonState::Released => {
930 state.button_pressed = None;
931
932 if let Some(window) = state.mouse_focused_window.clone() {
933 let input = PlatformInput::MouseUp(MouseUpEvent {
934 button,
935 position: state.mouse_location.unwrap(),
936 modifiers: state.modifiers,
937 click_count: state.click.current_count,
938 });
939 drop(state);
940 window.handle_input(input);
941 }
942 }
943 _ => {}
944 }
945 }
946
947 // Axis Events
948 wl_pointer::Event::AxisSource {
949 axis_source: WEnum::Value(axis_source),
950 } => {
951 state.axis_source = axis_source;
952 }
953 wl_pointer::Event::Axis {
954 time,
955 axis: WEnum::Value(axis),
956 value,
957 ..
958 } => {
959 let axis_source = state.axis_source;
960 let axis_modifier = match axis {
961 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
962 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
963 _ => 1.0,
964 };
965 let supports_relative_direction =
966 wl_pointer.version() >= wl_pointer::EVT_AXIS_RELATIVE_DIRECTION_SINCE;
967 state.scroll_event_received = true;
968 let scroll_delta = state
969 .continuous_scroll_delta
970 .get_or_insert(point(px(0.0), px(0.0)));
971 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
972 let modifier = 3.0;
973 match axis {
974 wl_pointer::Axis::VerticalScroll => {
975 scroll_delta.y += px(value as f32 * modifier * axis_modifier);
976 }
977 wl_pointer::Axis::HorizontalScroll => {
978 scroll_delta.x += px(value as f32 * modifier * axis_modifier);
979 }
980 _ => unreachable!(),
981 }
982 }
983 wl_pointer::Event::AxisDiscrete {
984 axis: WEnum::Value(axis),
985 discrete,
986 } => {
987 state.scroll_event_received = true;
988 let axis_modifier = match axis {
989 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
990 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
991 _ => 1.0,
992 };
993
994 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
995 let modifier = 3.0;
996
997 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
998 match axis {
999 wl_pointer::Axis::VerticalScroll => {
1000 scroll_delta.y += discrete as f32 * axis_modifier * modifier;
1001 }
1002 wl_pointer::Axis::HorizontalScroll => {
1003 scroll_delta.x += discrete as f32 * axis_modifier * modifier;
1004 }
1005 _ => unreachable!(),
1006 }
1007 }
1008 wl_pointer::Event::AxisRelativeDirection {
1009 axis: WEnum::Value(axis),
1010 direction: WEnum::Value(direction),
1011 } => match (axis, direction) {
1012 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Identical) => {
1013 state.vertical_modifier = -1.0
1014 }
1015 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Inverted) => {
1016 state.vertical_modifier = 1.0
1017 }
1018 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Identical) => {
1019 state.horizontal_modifier = -1.0
1020 }
1021 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Inverted) => {
1022 state.horizontal_modifier = 1.0
1023 }
1024 _ => unreachable!(),
1025 },
1026 wl_pointer::Event::AxisValue120 {
1027 axis: WEnum::Value(axis),
1028 value120,
1029 } => {
1030 state.scroll_event_received = true;
1031 let axis_modifier = match axis {
1032 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
1033 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
1034 _ => unreachable!(),
1035 };
1036
1037 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
1038 let wheel_percent = value120 as f32 / 120.0;
1039 match axis {
1040 wl_pointer::Axis::VerticalScroll => {
1041 scroll_delta.y += wheel_percent * axis_modifier;
1042 }
1043 wl_pointer::Axis::HorizontalScroll => {
1044 scroll_delta.x += wheel_percent * axis_modifier;
1045 }
1046 _ => unreachable!(),
1047 }
1048 }
1049 wl_pointer::Event::Frame => {
1050 if state.scroll_event_received {
1051 state.scroll_event_received = false;
1052 let continuous = state.continuous_scroll_delta.take();
1053 let discrete = state.discrete_scroll_delta.take();
1054 if let Some(continuous) = continuous {
1055 if let Some(window) = state.mouse_focused_window.clone() {
1056 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1057 position: state.mouse_location.unwrap(),
1058 delta: ScrollDelta::Pixels(continuous),
1059 modifiers: state.modifiers,
1060 touch_phase: TouchPhase::Moved,
1061 });
1062 drop(state);
1063 window.handle_input(input);
1064 }
1065 } else if let Some(discrete) = discrete {
1066 if let Some(window) = state.mouse_focused_window.clone() {
1067 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1068 position: state.mouse_location.unwrap(),
1069 delta: ScrollDelta::Lines(discrete),
1070 modifiers: state.modifiers,
1071 touch_phase: TouchPhase::Moved,
1072 });
1073 drop(state);
1074 window.handle_input(input);
1075 }
1076 }
1077 }
1078 }
1079 _ => {}
1080 }
1081 }
1082}
1083
1084impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientStatePtr {
1085 fn event(
1086 this: &mut Self,
1087 _: &wp_fractional_scale_v1::WpFractionalScaleV1,
1088 event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
1089 surface_id: &ObjectId,
1090 _: &Connection,
1091 _: &QueueHandle<Self>,
1092 ) {
1093 let client = this.get_client();
1094 let mut state = client.borrow_mut();
1095
1096 let Some(window) = get_window(&mut state, surface_id) else {
1097 return;
1098 };
1099
1100 drop(state);
1101 window.handle_fractional_scale_event(event);
1102 }
1103}
1104
1105impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
1106 for WaylandClientStatePtr
1107{
1108 fn event(
1109 this: &mut Self,
1110 _: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
1111 event: zxdg_toplevel_decoration_v1::Event,
1112 surface_id: &ObjectId,
1113 _: &Connection,
1114 _: &QueueHandle<Self>,
1115 ) {
1116 let client = this.get_client();
1117 let mut state = client.borrow_mut();
1118 let Some(window) = get_window(&mut state, surface_id) else {
1119 return;
1120 };
1121
1122 drop(state);
1123 window.handle_toplevel_decoration_event(event);
1124 }
1125}