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_clipboard(&self, item: crate::ClipboardItem) {
381 self.0.borrow_mut().clipboard.set_contents(item.text);
382 }
383
384 fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
385 self.0
386 .borrow_mut()
387 .clipboard
388 .get_contents()
389 .ok()
390 .map(|s| crate::ClipboardItem {
391 text: s,
392 metadata: None,
393 })
394 }
395}
396
397impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
398 fn event(
399 this: &mut Self,
400 registry: &wl_registry::WlRegistry,
401 event: wl_registry::Event,
402 _: &GlobalListContents,
403 _: &Connection,
404 qh: &QueueHandle<Self>,
405 ) {
406 let mut client = this.get_client();
407 let mut state = client.borrow_mut();
408
409 match event {
410 wl_registry::Event::Global {
411 name,
412 interface,
413 version,
414 } => match &interface[..] {
415 "wl_seat" => {
416 state.wl_pointer = None;
417 registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
418 }
419 "wl_output" => {
420 let output =
421 registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ());
422
423 state.output_scales.insert(output.id(), 1);
424 }
425 _ => {}
426 },
427 wl_registry::Event::GlobalRemove { name: _ } => {}
428 _ => {}
429 }
430 }
431}
432
433delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
434delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
435delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
436delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
437delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
438delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
439delegate_noop!(WaylandClientStatePtr: ignore wp_viewporter::WpViewporter);
440delegate_noop!(WaylandClientStatePtr: ignore wp_viewport::WpViewport);
441
442impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
443 fn event(
444 state: &mut WaylandClientStatePtr,
445 _: &wl_callback::WlCallback,
446 event: wl_callback::Event,
447 surface_id: &ObjectId,
448 _: &Connection,
449 qh: &QueueHandle<Self>,
450 ) {
451 let client = state.get_client();
452 let mut state = client.borrow_mut();
453 let Some(window) = get_window(&mut state, surface_id) else {
454 return;
455 };
456 drop(state);
457
458 match event {
459 wl_callback::Event::Done { callback_data } => {
460 window.frame(true);
461 }
462 _ => {}
463 }
464 }
465}
466
467fn get_window(
468 mut state: &mut RefMut<WaylandClientState>,
469 surface_id: &ObjectId,
470) -> Option<WaylandWindowStatePtr> {
471 state.windows.get(surface_id).cloned()
472}
473
474impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
475 fn event(
476 this: &mut Self,
477 surface: &wl_surface::WlSurface,
478 event: <wl_surface::WlSurface as Proxy>::Event,
479 _: &(),
480 _: &Connection,
481 _: &QueueHandle<Self>,
482 ) {
483 let mut client = this.get_client();
484 let mut state = client.borrow_mut();
485
486 let Some(window) = get_window(&mut state, &surface.id()) else {
487 return;
488 };
489 let scales = state.output_scales.clone();
490 drop(state);
491
492 window.handle_surface_event(event, scales);
493 }
494}
495
496impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
497 fn event(
498 this: &mut Self,
499 output: &wl_output::WlOutput,
500 event: <wl_output::WlOutput as Proxy>::Event,
501 _: &(),
502 _: &Connection,
503 _: &QueueHandle<Self>,
504 ) {
505 let mut client = this.get_client();
506 let mut state = client.borrow_mut();
507
508 let Some(mut output_scale) = state.output_scales.get_mut(&output.id()) else {
509 return;
510 };
511
512 match event {
513 wl_output::Event::Scale { factor } => {
514 *output_scale = factor;
515 }
516 _ => {}
517 }
518 }
519}
520
521impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
522 fn event(
523 state: &mut Self,
524 xdg_surface: &xdg_surface::XdgSurface,
525 event: xdg_surface::Event,
526 surface_id: &ObjectId,
527 _: &Connection,
528 _: &QueueHandle<Self>,
529 ) {
530 let client = state.get_client();
531 let mut state = client.borrow_mut();
532 let Some(window) = get_window(&mut state, surface_id) else {
533 return;
534 };
535 drop(state);
536 window.handle_xdg_surface_event(event);
537 }
538}
539
540impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
541 fn event(
542 this: &mut Self,
543 xdg_toplevel: &xdg_toplevel::XdgToplevel,
544 event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
545 surface_id: &ObjectId,
546 _: &Connection,
547 _: &QueueHandle<Self>,
548 ) {
549 let client = this.get_client();
550 let mut state = client.borrow_mut();
551 let Some(window) = get_window(&mut state, surface_id) else {
552 return;
553 };
554
555 drop(state);
556 let should_close = window.handle_toplevel_event(event);
557
558 if should_close {
559 this.drop_window(surface_id);
560 }
561 }
562}
563
564impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
565 fn event(
566 _: &mut Self,
567 wm_base: &xdg_wm_base::XdgWmBase,
568 event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
569 _: &(),
570 _: &Connection,
571 _: &QueueHandle<Self>,
572 ) {
573 if let xdg_wm_base::Event::Ping { serial } = event {
574 wm_base.pong(serial);
575 }
576 }
577}
578
579impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
580 fn event(
581 state: &mut Self,
582 seat: &wl_seat::WlSeat,
583 event: wl_seat::Event,
584 data: &(),
585 conn: &Connection,
586 qh: &QueueHandle<Self>,
587 ) {
588 if let wl_seat::Event::Capabilities {
589 capabilities: WEnum::Value(capabilities),
590 } = event
591 {
592 if capabilities.contains(wl_seat::Capability::Keyboard) {
593 seat.get_keyboard(qh, ());
594 }
595 if capabilities.contains(wl_seat::Capability::Pointer) {
596 let client = state.get_client();
597 let mut state = client.borrow_mut();
598 state.wl_pointer = Some(seat.get_pointer(qh, ()));
599 }
600 }
601 }
602}
603
604impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
605 fn event(
606 this: &mut Self,
607 keyboard: &wl_keyboard::WlKeyboard,
608 event: wl_keyboard::Event,
609 data: &(),
610 conn: &Connection,
611 qh: &QueueHandle<Self>,
612 ) {
613 let mut client = this.get_client();
614 let mut state = client.borrow_mut();
615 match event {
616 wl_keyboard::Event::RepeatInfo { rate, delay } => {
617 state.repeat.characters_per_second = rate as u32;
618 state.repeat.delay = Duration::from_millis(delay as u64);
619 }
620 wl_keyboard::Event::Keymap {
621 format: WEnum::Value(format),
622 fd,
623 size,
624 ..
625 } => {
626 assert_eq!(
627 format,
628 wl_keyboard::KeymapFormat::XkbV1,
629 "Unsupported keymap format"
630 );
631 let keymap = unsafe {
632 xkb::Keymap::new_from_fd(
633 &xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
634 fd,
635 size as usize,
636 XKB_KEYMAP_FORMAT_TEXT_V1,
637 KEYMAP_COMPILE_NO_FLAGS,
638 )
639 .log_err()
640 .flatten()
641 .expect("Failed to create keymap")
642 };
643 state.keymap_state = Some(xkb::State::new(&keymap));
644 }
645 wl_keyboard::Event::Enter { surface, .. } => {
646 state.keyboard_focused_window = get_window(&mut state, &surface.id());
647
648 if let Some(window) = state.keyboard_focused_window.clone() {
649 drop(state);
650 window.set_focused(true);
651 }
652 }
653 wl_keyboard::Event::Leave { surface, .. } => {
654 let keyboard_focused_window = get_window(&mut state, &surface.id());
655 state.keyboard_focused_window = None;
656
657 if let Some(window) = keyboard_focused_window {
658 drop(state);
659 window.set_focused(false);
660 }
661 }
662 wl_keyboard::Event::Modifiers {
663 mods_depressed,
664 mods_latched,
665 mods_locked,
666 group,
667 ..
668 } => {
669 let focused_window = state.keyboard_focused_window.clone();
670 let Some(focused_window) = focused_window else {
671 return;
672 };
673
674 let keymap_state = state.keymap_state.as_mut().unwrap();
675 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
676 state.modifiers = Modifiers::from_xkb(keymap_state);
677
678 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
679 modifiers: state.modifiers,
680 });
681
682 drop(state);
683 focused_window.handle_input(input);
684 }
685 wl_keyboard::Event::Key {
686 key,
687 state: WEnum::Value(key_state),
688 ..
689 } => {
690 let focused_window = state.keyboard_focused_window.clone();
691 let Some(focused_window) = focused_window else {
692 return;
693 };
694 let focused_window = focused_window.clone();
695
696 let keymap_state = state.keymap_state.as_ref().unwrap();
697 let keycode = Keycode::from(key + MIN_KEYCODE);
698 let keysym = keymap_state.key_get_one_sym(keycode);
699
700 match key_state {
701 wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
702 let input = PlatformInput::KeyDown(KeyDownEvent {
703 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
704 is_held: false, // todo(linux)
705 });
706
707 state.repeat.current_id += 1;
708 state.repeat.current_keysym = Some(keysym);
709
710 let rate = state.repeat.characters_per_second;
711 let id = state.repeat.current_id;
712 state
713 .loop_handle
714 .insert_source(Timer::from_duration(state.repeat.delay), {
715 let input = input.clone();
716 move |event, _metadata, this| {
717 let mut client = this.get_client();
718 let mut state = client.borrow_mut();
719 let is_repeating = id == state.repeat.current_id
720 && state.repeat.current_keysym.is_some()
721 && state.keyboard_focused_window.is_some();
722
723 if !is_repeating {
724 return TimeoutAction::Drop;
725 }
726
727 let focused_window =
728 state.keyboard_focused_window.as_ref().unwrap().clone();
729
730 drop(state);
731 focused_window.handle_input(input.clone());
732
733 TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
734 }
735 })
736 .unwrap();
737
738 drop(state);
739 focused_window.handle_input(input);
740 }
741 wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
742 let input = PlatformInput::KeyUp(KeyUpEvent {
743 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
744 });
745
746 state.repeat.current_keysym = None;
747
748 drop(state);
749 focused_window.handle_input(input);
750 }
751 _ => {}
752 }
753 }
754 _ => {}
755 }
756 }
757}
758
759fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
760 // These values are coming from <linux/input-event-codes.h>.
761 const BTN_LEFT: u32 = 0x110;
762 const BTN_RIGHT: u32 = 0x111;
763 const BTN_MIDDLE: u32 = 0x112;
764 const BTN_SIDE: u32 = 0x113;
765 const BTN_EXTRA: u32 = 0x114;
766 const BTN_FORWARD: u32 = 0x115;
767 const BTN_BACK: u32 = 0x116;
768
769 Some(match button {
770 BTN_LEFT => MouseButton::Left,
771 BTN_RIGHT => MouseButton::Right,
772 BTN_MIDDLE => MouseButton::Middle,
773 BTN_BACK | BTN_SIDE => MouseButton::Navigate(NavigationDirection::Back),
774 BTN_FORWARD | BTN_EXTRA => MouseButton::Navigate(NavigationDirection::Forward),
775 _ => return None,
776 })
777}
778
779impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
780 fn event(
781 this: &mut Self,
782 wl_pointer: &wl_pointer::WlPointer,
783 event: wl_pointer::Event,
784 data: &(),
785 conn: &Connection,
786 qh: &QueueHandle<Self>,
787 ) {
788 let mut client = this.get_client();
789 let mut state = client.borrow_mut();
790 let cursor_icon_name = state.cursor_icon_name.clone();
791
792 match event {
793 wl_pointer::Event::Enter {
794 serial,
795 surface,
796 surface_x,
797 surface_y,
798 ..
799 } => {
800 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
801
802 if let Some(window) = get_window(&mut state, &surface.id()) {
803 state.enter_token = Some(());
804 state.mouse_focused_window = Some(window.clone());
805 state.cursor.mark_dirty();
806 state.cursor.set_serial_id(serial);
807 state
808 .cursor
809 .set_icon(&wl_pointer, cursor_icon_name.as_str());
810 drop(state);
811 window.set_focused(true);
812 }
813 }
814 wl_pointer::Event::Leave { surface, .. } => {
815 if let Some(focused_window) = state.mouse_focused_window.clone() {
816 state.enter_token.take();
817 let input = PlatformInput::MouseExited(MouseExitEvent {
818 position: state.mouse_location.unwrap(),
819 pressed_button: state.button_pressed,
820 modifiers: state.modifiers,
821 });
822 state.mouse_focused_window = None;
823 state.mouse_location = None;
824
825 drop(state);
826 focused_window.handle_input(input);
827 focused_window.set_focused(false);
828 }
829 }
830 wl_pointer::Event::Motion {
831 time,
832 surface_x,
833 surface_y,
834 ..
835 } => {
836 if state.mouse_focused_window.is_none() {
837 return;
838 }
839 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
840
841 if let Some(window) = state.mouse_focused_window.clone() {
842 let input = PlatformInput::MouseMove(MouseMoveEvent {
843 position: state.mouse_location.unwrap(),
844 pressed_button: state.button_pressed,
845 modifiers: state.modifiers,
846 });
847 drop(state);
848 window.handle_input(input);
849 }
850 }
851 wl_pointer::Event::Button {
852 button,
853 state: WEnum::Value(button_state),
854 ..
855 } => {
856 let button = linux_button_to_gpui(button);
857 let Some(button) = button else { return };
858 if state.mouse_focused_window.is_none() {
859 return;
860 }
861 match button_state {
862 wl_pointer::ButtonState::Pressed => {
863 let click_elapsed = state.click.last_click.elapsed();
864
865 if click_elapsed < DOUBLE_CLICK_INTERVAL
866 && is_within_click_distance(
867 state.click.last_location,
868 state.mouse_location.unwrap(),
869 )
870 {
871 state.click.current_count += 1;
872 } else {
873 state.click.current_count = 1;
874 }
875
876 state.click.last_click = Instant::now();
877 state.click.last_location = state.mouse_location.unwrap();
878
879 state.button_pressed = Some(button);
880
881 if let Some(window) = state.mouse_focused_window.clone() {
882 let input = PlatformInput::MouseDown(MouseDownEvent {
883 button,
884 position: state.mouse_location.unwrap(),
885 modifiers: state.modifiers,
886 click_count: state.click.current_count,
887 first_mouse: state.enter_token.take().is_some(),
888 });
889 drop(state);
890 window.handle_input(input);
891 }
892 }
893 wl_pointer::ButtonState::Released => {
894 state.button_pressed = None;
895
896 if let Some(window) = state.mouse_focused_window.clone() {
897 let input = PlatformInput::MouseUp(MouseUpEvent {
898 button,
899 position: state.mouse_location.unwrap(),
900 modifiers: state.modifiers,
901 click_count: state.click.current_count,
902 });
903 drop(state);
904 window.handle_input(input);
905 }
906 }
907 _ => {}
908 }
909 }
910
911 // Axis Events
912 wl_pointer::Event::AxisSource {
913 axis_source: WEnum::Value(axis_source),
914 } => {
915 state.axis_source = axis_source;
916 }
917 wl_pointer::Event::Axis {
918 time,
919 axis: WEnum::Value(axis),
920 value,
921 ..
922 } => {
923 let axis_source = state.axis_source;
924 let axis_modifier = match axis {
925 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
926 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
927 _ => 1.0,
928 };
929 let supports_relative_direction =
930 wl_pointer.version() >= wl_pointer::EVT_AXIS_RELATIVE_DIRECTION_SINCE;
931 state.scroll_event_received = true;
932 let scroll_delta = state
933 .continuous_scroll_delta
934 .get_or_insert(point(px(0.0), px(0.0)));
935 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
936 let modifier = 3.0;
937 match axis {
938 wl_pointer::Axis::VerticalScroll => {
939 scroll_delta.y += px(value as f32 * modifier * axis_modifier);
940 }
941 wl_pointer::Axis::HorizontalScroll => {
942 scroll_delta.x += px(value as f32 * modifier * axis_modifier);
943 }
944 _ => unreachable!(),
945 }
946 }
947 wl_pointer::Event::AxisDiscrete {
948 axis: WEnum::Value(axis),
949 discrete,
950 } => {
951 state.scroll_event_received = true;
952 let axis_modifier = match axis {
953 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
954 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
955 _ => 1.0,
956 };
957
958 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
959 let modifier = 3.0;
960
961 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
962 match axis {
963 wl_pointer::Axis::VerticalScroll => {
964 scroll_delta.y += discrete as f32 * axis_modifier * modifier;
965 }
966 wl_pointer::Axis::HorizontalScroll => {
967 scroll_delta.x += discrete as f32 * axis_modifier * modifier;
968 }
969 _ => unreachable!(),
970 }
971 }
972 wl_pointer::Event::AxisRelativeDirection {
973 axis: WEnum::Value(axis),
974 direction: WEnum::Value(direction),
975 } => match (axis, direction) {
976 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Identical) => {
977 state.vertical_modifier = -1.0
978 }
979 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Inverted) => {
980 state.vertical_modifier = 1.0
981 }
982 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Identical) => {
983 state.horizontal_modifier = -1.0
984 }
985 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Inverted) => {
986 state.horizontal_modifier = 1.0
987 }
988 _ => unreachable!(),
989 },
990 wl_pointer::Event::AxisValue120 {
991 axis: WEnum::Value(axis),
992 value120,
993 } => {
994 state.scroll_event_received = true;
995 let axis_modifier = match axis {
996 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
997 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
998 _ => unreachable!(),
999 };
1000
1001 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
1002 let wheel_percent = value120 as f32 / 120.0;
1003 match axis {
1004 wl_pointer::Axis::VerticalScroll => {
1005 scroll_delta.y += wheel_percent * axis_modifier;
1006 }
1007 wl_pointer::Axis::HorizontalScroll => {
1008 scroll_delta.x += wheel_percent * axis_modifier;
1009 }
1010 _ => unreachable!(),
1011 }
1012 }
1013 wl_pointer::Event::Frame => {
1014 if state.scroll_event_received {
1015 state.scroll_event_received = false;
1016 let continuous = state.continuous_scroll_delta.take();
1017 let discrete = state.discrete_scroll_delta.take();
1018 if let Some(continuous) = continuous {
1019 if let Some(window) = state.mouse_focused_window.clone() {
1020 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1021 position: state.mouse_location.unwrap(),
1022 delta: ScrollDelta::Pixels(continuous),
1023 modifiers: state.modifiers,
1024 touch_phase: TouchPhase::Moved,
1025 });
1026 drop(state);
1027 window.handle_input(input);
1028 }
1029 } else if let Some(discrete) = discrete {
1030 if let Some(window) = state.mouse_focused_window.clone() {
1031 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1032 position: state.mouse_location.unwrap(),
1033 delta: ScrollDelta::Lines(discrete),
1034 modifiers: state.modifiers,
1035 touch_phase: TouchPhase::Moved,
1036 });
1037 drop(state);
1038 window.handle_input(input);
1039 }
1040 }
1041 }
1042 }
1043 _ => {}
1044 }
1045 }
1046}
1047
1048impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientStatePtr {
1049 fn event(
1050 this: &mut Self,
1051 _: &wp_fractional_scale_v1::WpFractionalScaleV1,
1052 event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
1053 surface_id: &ObjectId,
1054 _: &Connection,
1055 _: &QueueHandle<Self>,
1056 ) {
1057 let client = this.get_client();
1058 let mut state = client.borrow_mut();
1059
1060 let Some(window) = get_window(&mut state, surface_id) else {
1061 return;
1062 };
1063
1064 drop(state);
1065 window.handle_fractional_scale_event(event);
1066 }
1067}
1068
1069impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
1070 for WaylandClientStatePtr
1071{
1072 fn event(
1073 this: &mut Self,
1074 _: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
1075 event: zxdg_toplevel_decoration_v1::Event,
1076 surface_id: &ObjectId,
1077 _: &Connection,
1078 _: &QueueHandle<Self>,
1079 ) {
1080 let client = this.get_client();
1081 let mut state = client.borrow_mut();
1082 let Some(window) = get_window(&mut state, surface_id) else {
1083 return;
1084 };
1085
1086 drop(state);
1087 window.handle_toplevel_decoration_event(event);
1088 }
1089}