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