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