1use core::hash;
2use std::cell::{RefCell, RefMut};
3use std::os::fd::{AsRawFd, BorrowedFd};
4use std::path::PathBuf;
5use std::rc::{Rc, Weak};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9use async_task::Runnable;
10use calloop::timer::{TimeoutAction, Timer};
11use calloop::{EventLoop, LoopHandle};
12use calloop_wayland_source::WaylandSource;
13use collections::HashMap;
14use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
15use copypasta::ClipboardProvider;
16use filedescriptor::Pipe;
17use smallvec::SmallVec;
18use util::ResultExt;
19use wayland_backend::client::ObjectId;
20use wayland_backend::protocol::WEnum;
21use wayland_client::event_created_child;
22use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};
23use wayland_client::protocol::wl_callback::{self, WlCallback};
24use wayland_client::protocol::wl_data_device_manager::DndAction;
25use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
26use wayland_client::protocol::{
27 wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output,
28};
29use wayland_client::{
30 delegate_noop,
31 protocol::{
32 wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,
33 wl_shm_pool, wl_surface,
34 },
35 Connection, Dispatch, Proxy, QueueHandle,
36};
37use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
38use wayland_protocols::wp::cursor_shape::v1::client::{
39 wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1,
40};
41use wayland_protocols::wp::fractional_scale::v1::client::{
42 wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
43};
44use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
45use wayland_protocols::xdg::decoration::zv1::client::{
46 zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
47};
48use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
49use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
50use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
51
52use super::super::{read_fd, DOUBLE_CLICK_INTERVAL};
53use super::window::{WaylandWindowState, WaylandWindowStatePtr};
54use crate::platform::linux::is_within_click_distance;
55use crate::platform::linux::wayland::cursor::Cursor;
56use crate::platform::linux::wayland::window::WaylandWindow;
57use crate::platform::linux::LinuxClient;
58use crate::platform::PlatformWindow;
59use crate::{point, px, FileDropEvent, ForegroundExecutor, MouseExitEvent};
60use crate::{
61 AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
62 ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
63 NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
64 ScrollWheelEvent, TouchPhase,
65};
66use crate::{LinuxCommon, WindowParams};
67
68/// Used to convert evdev scancode to xkb scancode
69const MIN_KEYCODE: u32 = 8;
70
71#[derive(Clone)]
72pub struct Globals {
73 pub qh: QueueHandle<WaylandClientStatePtr>,
74 pub compositor: wl_compositor::WlCompositor,
75 pub cursor_shape_manager: Option<wp_cursor_shape_manager_v1::WpCursorShapeManagerV1>,
76 pub data_device_manager: Option<wl_data_device_manager::WlDataDeviceManager>,
77 pub wm_base: xdg_wm_base::XdgWmBase,
78 pub shm: wl_shm::WlShm,
79 pub viewporter: Option<wp_viewporter::WpViewporter>,
80 pub fractional_scale_manager:
81 Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
82 pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
83 pub executor: ForegroundExecutor,
84}
85
86impl Globals {
87 fn new(
88 globals: GlobalList,
89 executor: ForegroundExecutor,
90 qh: QueueHandle<WaylandClientStatePtr>,
91 ) -> Self {
92 Globals {
93 compositor: globals
94 .bind(
95 &qh,
96 wl_surface::REQ_SET_BUFFER_SCALE_SINCE
97 ..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE,
98 (),
99 )
100 .unwrap(),
101 cursor_shape_manager: globals.bind(&qh, 1..=1, ()).ok(),
102 data_device_manager: globals
103 .bind(
104 &qh,
105 WL_DATA_DEVICE_MANAGER_VERSION..=WL_DATA_DEVICE_MANAGER_VERSION,
106 (),
107 )
108 .ok(),
109 shm: globals.bind(&qh, 1..=1, ()).unwrap(),
110 wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
111 viewporter: globals.bind(&qh, 1..=1, ()).ok(),
112 fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
113 decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
114 executor,
115 qh,
116 }
117 }
118}
119
120pub(crate) struct WaylandClientState {
121 serial: u32, // todo(linux): storing a general serial is wrong
122 pointer_serial: u32,
123 globals: Globals,
124 wl_pointer: Option<wl_pointer::WlPointer>,
125 cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
126 data_device: Option<wl_data_device::WlDataDevice>,
127 // Surface to Window mapping
128 windows: HashMap<ObjectId, WaylandWindowStatePtr>,
129 // Output to scale mapping
130 output_scales: HashMap<ObjectId, i32>,
131 keymap_state: Option<xkb::State>,
132 drag: DragState,
133 click: ClickState,
134 repeat: KeyRepeat,
135 modifiers: Modifiers,
136 axis_source: AxisSource,
137 mouse_location: Option<Point<Pixels>>,
138 continuous_scroll_delta: Option<Point<Pixels>>,
139 discrete_scroll_delta: Option<Point<f32>>,
140 vertical_modifier: f32,
141 horizontal_modifier: f32,
142 scroll_event_received: bool,
143 enter_token: Option<()>,
144 button_pressed: Option<MouseButton>,
145 mouse_focused_window: Option<WaylandWindowStatePtr>,
146 keyboard_focused_window: Option<WaylandWindowStatePtr>,
147 loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
148 cursor_style: Option<CursorStyle>,
149 cursor: Cursor,
150 clipboard: Option<Clipboard>,
151 primary: Option<Primary>,
152 event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
153 common: LinuxCommon,
154}
155
156pub struct DragState {
157 data_offer: Option<wl_data_offer::WlDataOffer>,
158 window: Option<WaylandWindowStatePtr>,
159 position: Point<Pixels>,
160}
161
162pub struct ClickState {
163 last_click: Instant,
164 last_location: Point<Pixels>,
165 current_count: usize,
166}
167
168pub(crate) struct KeyRepeat {
169 characters_per_second: u32,
170 delay: Duration,
171 current_id: u64,
172 current_keycode: Option<xkb::Keycode>,
173}
174
175/// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the
176/// window to GPUI.
177#[derive(Clone)]
178pub struct WaylandClientStatePtr(Weak<RefCell<WaylandClientState>>);
179
180impl WaylandClientStatePtr {
181 fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {
182 self.0
183 .upgrade()
184 .expect("The pointer should always be valid when dispatching in wayland")
185 }
186
187 pub fn drop_window(&self, surface_id: &ObjectId) {
188 let mut client = self.get_client();
189 let mut state = client.borrow_mut();
190 let closed_window = state.windows.remove(surface_id).unwrap();
191 if let Some(window) = state.mouse_focused_window.take() {
192 if !window.ptr_eq(&closed_window) {
193 state.mouse_focused_window = Some(window);
194 }
195 }
196 if let Some(window) = state.keyboard_focused_window.take() {
197 if !window.ptr_eq(&closed_window) {
198 state.mouse_focused_window = Some(window);
199 }
200 }
201 if state.windows.is_empty() {
202 state.common.signal.stop();
203 }
204 }
205}
206
207#[derive(Clone)]
208pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
209
210impl Drop for WaylandClient {
211 fn drop(&mut self) {
212 let mut state = self.0.borrow_mut();
213 state.windows.clear();
214
215 // Drop the clipboard to prevent a seg fault after we've closed all Wayland connections.
216 state.primary = None;
217 state.clipboard = None;
218 if let Some(wl_pointer) = &state.wl_pointer {
219 wl_pointer.release();
220 }
221 if let Some(cursor_shape_device) = &state.cursor_shape_device {
222 cursor_shape_device.destroy();
223 }
224 if let Some(data_device) = &state.data_device {
225 data_device.release();
226 }
227 }
228}
229
230const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3;
231const WL_OUTPUT_VERSION: u32 = 2;
232
233fn wl_seat_version(version: u32) -> u32 {
234 // We rely on the wl_pointer.frame event
235 const WL_SEAT_MIN_VERSION: u32 = 5;
236 const WL_SEAT_MAX_VERSION: u32 = 9;
237
238 if version < WL_SEAT_MIN_VERSION {
239 panic!(
240 "wl_seat below required version: {} < {}",
241 version, WL_SEAT_MIN_VERSION
242 );
243 }
244
245 version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)
246}
247
248impl WaylandClient {
249 pub(crate) fn new() -> Self {
250 let conn = Connection::connect_to_env().unwrap();
251
252 let (globals, mut event_queue) =
253 registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
254 let qh = event_queue.handle();
255
256 let mut seat: Option<wl_seat::WlSeat> = None;
257 let mut outputs = HashMap::default();
258 globals.contents().with_list(|list| {
259 for global in list {
260 match &global.interface[..] {
261 "wl_seat" => {
262 // TODO: multi-seat support
263 seat = Some(globals.registry().bind::<wl_seat::WlSeat, _, _>(
264 global.name,
265 wl_seat_version(global.version),
266 &qh,
267 (),
268 ));
269 }
270 "wl_output" => {
271 let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
272 global.name,
273 WL_OUTPUT_VERSION,
274 &qh,
275 (),
276 );
277 outputs.insert(output.id(), 1);
278 }
279 _ => {}
280 }
281 }
282 });
283
284 let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
285
286 let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
287
288 let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
289
290 let handle = event_loop.handle();
291 handle.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
292 if let calloop::channel::Event::Msg(runnable) = event {
293 runnable.run();
294 }
295 });
296
297 let seat = seat.unwrap();
298 let globals = Globals::new(globals, common.foreground_executor.clone(), qh.clone());
299
300 let data_device = globals
301 .data_device_manager
302 .as_ref()
303 .map(|data_device_manager| data_device_manager.get_data_device(&seat, &qh, ()));
304
305 let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
306
307 let cursor = Cursor::new(&conn, &globals, 24);
308
309 let mut state = Rc::new(RefCell::new(WaylandClientState {
310 serial: 0,
311 pointer_serial: 0,
312 globals,
313 wl_pointer: None,
314 cursor_shape_device: None,
315 data_device,
316 output_scales: outputs,
317 windows: HashMap::default(),
318 common,
319 keymap_state: None,
320 drag: DragState {
321 data_offer: None,
322 window: None,
323 position: Point::default(),
324 },
325 click: ClickState {
326 last_click: Instant::now(),
327 last_location: Point::default(),
328 current_count: 0,
329 },
330 repeat: KeyRepeat {
331 characters_per_second: 16,
332 delay: Duration::from_millis(500),
333 current_id: 0,
334 current_keycode: None,
335 },
336 modifiers: Modifiers {
337 shift: false,
338 control: false,
339 alt: false,
340 function: false,
341 platform: false,
342 },
343 scroll_event_received: false,
344 axis_source: AxisSource::Wheel,
345 mouse_location: None,
346 continuous_scroll_delta: None,
347 discrete_scroll_delta: None,
348 vertical_modifier: -1.0,
349 horizontal_modifier: -1.0,
350 button_pressed: None,
351 mouse_focused_window: None,
352 keyboard_focused_window: None,
353 loop_handle: handle.clone(),
354 enter_token: None,
355 cursor_style: None,
356 cursor,
357 clipboard: Some(clipboard),
358 primary: Some(primary),
359 event_loop: Some(event_loop),
360 }));
361
362 WaylandSource::new(conn, event_queue).insert(handle);
363
364 Self(state)
365 }
366}
367
368impl LinuxClient for WaylandClient {
369 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
370 Vec::new()
371 }
372
373 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
374 unimplemented!()
375 }
376
377 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
378 None
379 }
380
381 fn open_window(
382 &self,
383 handle: AnyWindowHandle,
384 params: WindowParams,
385 ) -> Box<dyn PlatformWindow> {
386 let mut state = self.0.borrow_mut();
387
388 let (window, surface_id) = WaylandWindow::new(
389 state.globals.clone(),
390 WaylandClientStatePtr(Rc::downgrade(&self.0)),
391 params,
392 );
393 state.windows.insert(surface_id, window.0.clone());
394
395 Box::new(window)
396 }
397
398 fn set_cursor_style(&self, style: CursorStyle) {
399 let mut state = self.0.borrow_mut();
400
401 let need_update = state
402 .cursor_style
403 .map_or(true, |current_style| current_style != style);
404
405 if need_update {
406 let serial = state.pointer_serial;
407 state.cursor_style = Some(style);
408
409 if let Some(cursor_shape_device) = &state.cursor_shape_device {
410 cursor_shape_device.set_shape(serial, style.to_shape());
411 } else if state.mouse_focused_window.is_some() {
412 // cursor-shape-v1 isn't supported, set the cursor using a surface.
413 let wl_pointer = state
414 .wl_pointer
415 .clone()
416 .expect("window is focused by pointer");
417 state
418 .cursor
419 .set_icon(&wl_pointer, serial, &style.to_icon_name());
420 }
421 }
422 }
423
424 fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
425 f(&mut self.0.borrow_mut().common)
426 }
427
428 fn run(&self) {
429 let mut event_loop = self
430 .0
431 .borrow_mut()
432 .event_loop
433 .take()
434 .expect("App is already running");
435
436 event_loop
437 .run(
438 None,
439 &mut WaylandClientStatePtr(Rc::downgrade(&self.0)),
440 |_| {},
441 )
442 .log_err();
443 }
444
445 fn write_to_primary(&self, item: crate::ClipboardItem) {
446 self.0
447 .borrow_mut()
448 .primary
449 .as_mut()
450 .unwrap()
451 .set_contents(item.text);
452 }
453
454 fn write_to_clipboard(&self, item: crate::ClipboardItem) {
455 self.0
456 .borrow_mut()
457 .clipboard
458 .as_mut()
459 .unwrap()
460 .set_contents(item.text);
461 }
462
463 fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
464 self.0
465 .borrow_mut()
466 .primary
467 .as_mut()
468 .unwrap()
469 .get_contents()
470 .ok()
471 .map(|s| crate::ClipboardItem {
472 text: s,
473 metadata: None,
474 })
475 }
476
477 fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
478 self.0
479 .borrow_mut()
480 .clipboard
481 .as_mut()
482 .unwrap()
483 .get_contents()
484 .ok()
485 .map(|s| crate::ClipboardItem {
486 text: s,
487 metadata: None,
488 })
489 }
490}
491
492impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
493 fn event(
494 this: &mut Self,
495 registry: &wl_registry::WlRegistry,
496 event: wl_registry::Event,
497 _: &GlobalListContents,
498 _: &Connection,
499 qh: &QueueHandle<Self>,
500 ) {
501 let mut client = this.get_client();
502 let mut state = client.borrow_mut();
503
504 match event {
505 wl_registry::Event::Global {
506 name,
507 interface,
508 version,
509 } => match &interface[..] {
510 "wl_seat" => {
511 state.wl_pointer = None;
512 registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
513 }
514 "wl_output" => {
515 let output =
516 registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ());
517
518 state.output_scales.insert(output.id(), 1);
519 }
520 _ => {}
521 },
522 wl_registry::Event::GlobalRemove { name: _ } => {}
523 _ => {}
524 }
525 }
526}
527
528delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
529delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_device_v1::WpCursorShapeDeviceV1);
530delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_manager_v1::WpCursorShapeManagerV1);
531delegate_noop!(WaylandClientStatePtr: ignore wl_data_device_manager::WlDataDeviceManager);
532delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
533delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
534delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
535delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
536delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
537delegate_noop!(WaylandClientStatePtr: ignore wp_viewporter::WpViewporter);
538delegate_noop!(WaylandClientStatePtr: ignore wp_viewport::WpViewport);
539
540impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
541 fn event(
542 state: &mut WaylandClientStatePtr,
543 _: &wl_callback::WlCallback,
544 event: wl_callback::Event,
545 surface_id: &ObjectId,
546 _: &Connection,
547 qh: &QueueHandle<Self>,
548 ) {
549 let client = state.get_client();
550 let mut state = client.borrow_mut();
551 let Some(window) = get_window(&mut state, surface_id) else {
552 return;
553 };
554 drop(state);
555
556 match event {
557 wl_callback::Event::Done { callback_data } => {
558 window.frame(true);
559 }
560 _ => {}
561 }
562 }
563}
564
565fn get_window(
566 mut state: &mut RefMut<WaylandClientState>,
567 surface_id: &ObjectId,
568) -> Option<WaylandWindowStatePtr> {
569 state.windows.get(surface_id).cloned()
570}
571
572impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
573 fn event(
574 this: &mut Self,
575 surface: &wl_surface::WlSurface,
576 event: <wl_surface::WlSurface as Proxy>::Event,
577 _: &(),
578 _: &Connection,
579 _: &QueueHandle<Self>,
580 ) {
581 let mut client = this.get_client();
582 let mut state = client.borrow_mut();
583
584 let Some(window) = get_window(&mut state, &surface.id()) else {
585 return;
586 };
587 let scales = state.output_scales.clone();
588 drop(state);
589
590 window.handle_surface_event(event, scales);
591 }
592}
593
594impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
595 fn event(
596 this: &mut Self,
597 output: &wl_output::WlOutput,
598 event: <wl_output::WlOutput as Proxy>::Event,
599 _: &(),
600 _: &Connection,
601 _: &QueueHandle<Self>,
602 ) {
603 let mut client = this.get_client();
604 let mut state = client.borrow_mut();
605
606 let Some(mut output_scale) = state.output_scales.get_mut(&output.id()) else {
607 return;
608 };
609
610 match event {
611 wl_output::Event::Scale { factor } => {
612 *output_scale = factor;
613 }
614 _ => {}
615 }
616 }
617}
618
619impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
620 fn event(
621 state: &mut Self,
622 xdg_surface: &xdg_surface::XdgSurface,
623 event: xdg_surface::Event,
624 surface_id: &ObjectId,
625 _: &Connection,
626 _: &QueueHandle<Self>,
627 ) {
628 let client = state.get_client();
629 let mut state = client.borrow_mut();
630 let Some(window) = get_window(&mut state, surface_id) else {
631 return;
632 };
633 drop(state);
634 window.handle_xdg_surface_event(event);
635 }
636}
637
638impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
639 fn event(
640 this: &mut Self,
641 xdg_toplevel: &xdg_toplevel::XdgToplevel,
642 event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
643 surface_id: &ObjectId,
644 _: &Connection,
645 _: &QueueHandle<Self>,
646 ) {
647 let client = this.get_client();
648 let mut state = client.borrow_mut();
649 let Some(window) = get_window(&mut state, surface_id) else {
650 return;
651 };
652
653 drop(state);
654 let should_close = window.handle_toplevel_event(event);
655
656 if should_close {
657 this.drop_window(surface_id);
658 }
659 }
660}
661
662impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
663 fn event(
664 this: &mut Self,
665 wm_base: &xdg_wm_base::XdgWmBase,
666 event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
667 _: &(),
668 _: &Connection,
669 _: &QueueHandle<Self>,
670 ) {
671 if let xdg_wm_base::Event::Ping { serial } = event {
672 let client = this.get_client();
673 let mut state = client.borrow_mut();
674 state.serial = serial;
675 wm_base.pong(serial);
676 }
677 }
678}
679
680impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
681 fn event(
682 state: &mut Self,
683 seat: &wl_seat::WlSeat,
684 event: wl_seat::Event,
685 data: &(),
686 conn: &Connection,
687 qh: &QueueHandle<Self>,
688 ) {
689 if let wl_seat::Event::Capabilities {
690 capabilities: WEnum::Value(capabilities),
691 } = event
692 {
693 if capabilities.contains(wl_seat::Capability::Keyboard) {
694 seat.get_keyboard(qh, ());
695 }
696 if capabilities.contains(wl_seat::Capability::Pointer) {
697 let client = state.get_client();
698 let mut state = client.borrow_mut();
699 let pointer = seat.get_pointer(qh, ());
700 state.cursor_shape_device = state
701 .globals
702 .cursor_shape_manager
703 .as_ref()
704 .map(|cursor_shape_manager| cursor_shape_manager.get_pointer(&pointer, qh, ()));
705 state.wl_pointer = Some(pointer);
706 }
707 }
708 }
709}
710
711impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
712 fn event(
713 this: &mut Self,
714 keyboard: &wl_keyboard::WlKeyboard,
715 event: wl_keyboard::Event,
716 data: &(),
717 conn: &Connection,
718 qh: &QueueHandle<Self>,
719 ) {
720 let mut client = this.get_client();
721 let mut state = client.borrow_mut();
722 match event {
723 wl_keyboard::Event::RepeatInfo { rate, delay } => {
724 state.repeat.characters_per_second = rate as u32;
725 state.repeat.delay = Duration::from_millis(delay as u64);
726 }
727 wl_keyboard::Event::Keymap {
728 format: WEnum::Value(format),
729 fd,
730 size,
731 ..
732 } => {
733 assert_eq!(
734 format,
735 wl_keyboard::KeymapFormat::XkbV1,
736 "Unsupported keymap format"
737 );
738 let keymap = unsafe {
739 xkb::Keymap::new_from_fd(
740 &xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
741 fd,
742 size as usize,
743 XKB_KEYMAP_FORMAT_TEXT_V1,
744 KEYMAP_COMPILE_NO_FLAGS,
745 )
746 .log_err()
747 .flatten()
748 .expect("Failed to create keymap")
749 };
750 state.keymap_state = Some(xkb::State::new(&keymap));
751 }
752 wl_keyboard::Event::Enter {
753 serial, surface, ..
754 } => {
755 state.serial = serial;
756 state.keyboard_focused_window = get_window(&mut state, &surface.id());
757
758 if let Some(window) = state.keyboard_focused_window.clone() {
759 drop(state);
760 window.set_focused(true);
761 }
762 }
763 wl_keyboard::Event::Leave {
764 serial, surface, ..
765 } => {
766 state.serial = serial;
767 let keyboard_focused_window = get_window(&mut state, &surface.id());
768 state.keyboard_focused_window = None;
769
770 if let Some(window) = keyboard_focused_window {
771 drop(state);
772 window.set_focused(false);
773 }
774 }
775 wl_keyboard::Event::Modifiers {
776 serial,
777 mods_depressed,
778 mods_latched,
779 mods_locked,
780 group,
781 ..
782 } => {
783 state.serial = serial;
784 let focused_window = state.keyboard_focused_window.clone();
785 let Some(focused_window) = focused_window else {
786 return;
787 };
788
789 let keymap_state = state.keymap_state.as_mut().unwrap();
790 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
791 state.modifiers = Modifiers::from_xkb(keymap_state);
792
793 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
794 modifiers: state.modifiers,
795 });
796
797 drop(state);
798 focused_window.handle_input(input);
799 }
800 wl_keyboard::Event::Key {
801 key,
802 state: WEnum::Value(key_state),
803 serial,
804 ..
805 } => {
806 state.serial = serial;
807
808 let focused_window = state.keyboard_focused_window.clone();
809 let Some(focused_window) = focused_window else {
810 return;
811 };
812 let focused_window = focused_window.clone();
813
814 let keymap_state = state.keymap_state.as_ref().unwrap();
815 let keycode = Keycode::from(key + MIN_KEYCODE);
816 let keysym = keymap_state.key_get_one_sym(keycode);
817
818 match key_state {
819 wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
820 let input = PlatformInput::KeyDown(KeyDownEvent {
821 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
822 is_held: false, // todo(linux)
823 });
824
825 state.repeat.current_id += 1;
826 state.repeat.current_keycode = Some(keycode);
827
828 let rate = state.repeat.characters_per_second;
829 let id = state.repeat.current_id;
830 state
831 .loop_handle
832 .insert_source(Timer::from_duration(state.repeat.delay), {
833 let input = input.clone();
834 move |event, _metadata, this| {
835 let mut client = this.get_client();
836 let mut state = client.borrow_mut();
837 let is_repeating = id == state.repeat.current_id
838 && state.repeat.current_keycode.is_some()
839 && state.keyboard_focused_window.is_some();
840
841 if !is_repeating {
842 return TimeoutAction::Drop;
843 }
844
845 let focused_window =
846 state.keyboard_focused_window.as_ref().unwrap().clone();
847
848 drop(state);
849 focused_window.handle_input(input.clone());
850
851 TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
852 }
853 })
854 .unwrap();
855
856 drop(state);
857 focused_window.handle_input(input);
858 }
859 wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
860 let input = PlatformInput::KeyUp(KeyUpEvent {
861 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
862 });
863
864 if state.repeat.current_keycode == Some(keycode) {
865 state.repeat.current_keycode = None;
866 }
867
868 drop(state);
869 focused_window.handle_input(input);
870 }
871 _ => {}
872 }
873 }
874 _ => {}
875 }
876 }
877}
878
879fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
880 // These values are coming from <linux/input-event-codes.h>.
881 const BTN_LEFT: u32 = 0x110;
882 const BTN_RIGHT: u32 = 0x111;
883 const BTN_MIDDLE: u32 = 0x112;
884 const BTN_SIDE: u32 = 0x113;
885 const BTN_EXTRA: u32 = 0x114;
886 const BTN_FORWARD: u32 = 0x115;
887 const BTN_BACK: u32 = 0x116;
888
889 Some(match button {
890 BTN_LEFT => MouseButton::Left,
891 BTN_RIGHT => MouseButton::Right,
892 BTN_MIDDLE => MouseButton::Middle,
893 BTN_BACK | BTN_SIDE => MouseButton::Navigate(NavigationDirection::Back),
894 BTN_FORWARD | BTN_EXTRA => MouseButton::Navigate(NavigationDirection::Forward),
895 _ => return None,
896 })
897}
898
899impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
900 fn event(
901 this: &mut Self,
902 wl_pointer: &wl_pointer::WlPointer,
903 event: wl_pointer::Event,
904 data: &(),
905 conn: &Connection,
906 qh: &QueueHandle<Self>,
907 ) {
908 let mut client = this.get_client();
909 let mut state = client.borrow_mut();
910
911 match event {
912 wl_pointer::Event::Enter {
913 serial,
914 surface,
915 surface_x,
916 surface_y,
917 ..
918 } => {
919 state.pointer_serial = serial;
920 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
921
922 if let Some(window) = get_window(&mut state, &surface.id()) {
923 state.enter_token = Some(());
924 state.mouse_focused_window = Some(window.clone());
925 if let Some(style) = state.cursor_style {
926 if let Some(cursor_shape_device) = &state.cursor_shape_device {
927 cursor_shape_device.set_shape(serial, style.to_shape());
928 } else {
929 state
930 .cursor
931 .set_icon(&wl_pointer, serial, &style.to_icon_name());
932 }
933 }
934 drop(state);
935 window.set_focused(true);
936 }
937 }
938 wl_pointer::Event::Leave { surface, .. } => {
939 if let Some(focused_window) = state.mouse_focused_window.clone() {
940 state.enter_token.take();
941 let input = PlatformInput::MouseExited(MouseExitEvent {
942 position: state.mouse_location.unwrap(),
943 pressed_button: state.button_pressed,
944 modifiers: state.modifiers,
945 });
946 state.mouse_focused_window = None;
947 state.mouse_location = None;
948
949 drop(state);
950 focused_window.handle_input(input);
951 focused_window.set_focused(false);
952 }
953 }
954 wl_pointer::Event::Motion {
955 time,
956 surface_x,
957 surface_y,
958 ..
959 } => {
960 if state.mouse_focused_window.is_none() {
961 return;
962 }
963 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
964
965 if let Some(window) = state.mouse_focused_window.clone() {
966 let input = PlatformInput::MouseMove(MouseMoveEvent {
967 position: state.mouse_location.unwrap(),
968 pressed_button: state.button_pressed,
969 modifiers: state.modifiers,
970 });
971 drop(state);
972 window.handle_input(input);
973 }
974 }
975 wl_pointer::Event::Button {
976 serial,
977 button,
978 state: WEnum::Value(button_state),
979 ..
980 } => {
981 state.serial = serial;
982 let button = linux_button_to_gpui(button);
983 let Some(button) = button else { return };
984 if state.mouse_focused_window.is_none() {
985 return;
986 }
987 match button_state {
988 wl_pointer::ButtonState::Pressed => {
989 let click_elapsed = state.click.last_click.elapsed();
990
991 if click_elapsed < DOUBLE_CLICK_INTERVAL
992 && is_within_click_distance(
993 state.click.last_location,
994 state.mouse_location.unwrap(),
995 )
996 {
997 state.click.current_count += 1;
998 } else {
999 state.click.current_count = 1;
1000 }
1001
1002 state.click.last_click = Instant::now();
1003 state.click.last_location = state.mouse_location.unwrap();
1004
1005 state.button_pressed = Some(button);
1006
1007 if let Some(window) = state.mouse_focused_window.clone() {
1008 let input = PlatformInput::MouseDown(MouseDownEvent {
1009 button,
1010 position: state.mouse_location.unwrap(),
1011 modifiers: state.modifiers,
1012 click_count: state.click.current_count,
1013 first_mouse: state.enter_token.take().is_some(),
1014 });
1015 drop(state);
1016 window.handle_input(input);
1017 }
1018 }
1019 wl_pointer::ButtonState::Released => {
1020 state.button_pressed = None;
1021
1022 if let Some(window) = state.mouse_focused_window.clone() {
1023 let input = PlatformInput::MouseUp(MouseUpEvent {
1024 button,
1025 position: state.mouse_location.unwrap(),
1026 modifiers: state.modifiers,
1027 click_count: state.click.current_count,
1028 });
1029 drop(state);
1030 window.handle_input(input);
1031 }
1032 }
1033 _ => {}
1034 }
1035 }
1036
1037 // Axis Events
1038 wl_pointer::Event::AxisSource {
1039 axis_source: WEnum::Value(axis_source),
1040 } => {
1041 state.axis_source = axis_source;
1042 }
1043 wl_pointer::Event::Axis {
1044 time,
1045 axis: WEnum::Value(axis),
1046 value,
1047 ..
1048 } => {
1049 let axis_source = state.axis_source;
1050 let axis_modifier = match axis {
1051 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
1052 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
1053 _ => 1.0,
1054 };
1055 let supports_relative_direction =
1056 wl_pointer.version() >= wl_pointer::EVT_AXIS_RELATIVE_DIRECTION_SINCE;
1057 state.scroll_event_received = true;
1058 let scroll_delta = state
1059 .continuous_scroll_delta
1060 .get_or_insert(point(px(0.0), px(0.0)));
1061 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
1062 let modifier = 3.0;
1063 match axis {
1064 wl_pointer::Axis::VerticalScroll => {
1065 scroll_delta.y += px(value as f32 * modifier * axis_modifier);
1066 }
1067 wl_pointer::Axis::HorizontalScroll => {
1068 scroll_delta.x += px(value as f32 * modifier * axis_modifier);
1069 }
1070 _ => unreachable!(),
1071 }
1072 }
1073 wl_pointer::Event::AxisDiscrete {
1074 axis: WEnum::Value(axis),
1075 discrete,
1076 } => {
1077 state.scroll_event_received = true;
1078 let axis_modifier = match axis {
1079 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
1080 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
1081 _ => 1.0,
1082 };
1083
1084 // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
1085 let modifier = 3.0;
1086
1087 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
1088 match axis {
1089 wl_pointer::Axis::VerticalScroll => {
1090 scroll_delta.y += discrete as f32 * axis_modifier * modifier;
1091 }
1092 wl_pointer::Axis::HorizontalScroll => {
1093 scroll_delta.x += discrete as f32 * axis_modifier * modifier;
1094 }
1095 _ => unreachable!(),
1096 }
1097 }
1098 wl_pointer::Event::AxisRelativeDirection {
1099 axis: WEnum::Value(axis),
1100 direction: WEnum::Value(direction),
1101 } => match (axis, direction) {
1102 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Identical) => {
1103 state.vertical_modifier = -1.0
1104 }
1105 (wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Inverted) => {
1106 state.vertical_modifier = 1.0
1107 }
1108 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Identical) => {
1109 state.horizontal_modifier = -1.0
1110 }
1111 (wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Inverted) => {
1112 state.horizontal_modifier = 1.0
1113 }
1114 _ => unreachable!(),
1115 },
1116 wl_pointer::Event::AxisValue120 {
1117 axis: WEnum::Value(axis),
1118 value120,
1119 } => {
1120 state.scroll_event_received = true;
1121 let axis_modifier = match axis {
1122 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
1123 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
1124 _ => unreachable!(),
1125 };
1126
1127 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
1128 let wheel_percent = value120 as f32 / 120.0;
1129 match axis {
1130 wl_pointer::Axis::VerticalScroll => {
1131 scroll_delta.y += wheel_percent * axis_modifier;
1132 }
1133 wl_pointer::Axis::HorizontalScroll => {
1134 scroll_delta.x += wheel_percent * axis_modifier;
1135 }
1136 _ => unreachable!(),
1137 }
1138 }
1139 wl_pointer::Event::Frame => {
1140 if state.scroll_event_received {
1141 state.scroll_event_received = false;
1142 let continuous = state.continuous_scroll_delta.take();
1143 let discrete = state.discrete_scroll_delta.take();
1144 if let Some(continuous) = continuous {
1145 if let Some(window) = state.mouse_focused_window.clone() {
1146 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1147 position: state.mouse_location.unwrap(),
1148 delta: ScrollDelta::Pixels(continuous),
1149 modifiers: state.modifiers,
1150 touch_phase: TouchPhase::Moved,
1151 });
1152 drop(state);
1153 window.handle_input(input);
1154 }
1155 } else if let Some(discrete) = discrete {
1156 if let Some(window) = state.mouse_focused_window.clone() {
1157 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
1158 position: state.mouse_location.unwrap(),
1159 delta: ScrollDelta::Lines(discrete),
1160 modifiers: state.modifiers,
1161 touch_phase: TouchPhase::Moved,
1162 });
1163 drop(state);
1164 window.handle_input(input);
1165 }
1166 }
1167 }
1168 }
1169 _ => {}
1170 }
1171 }
1172}
1173
1174impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientStatePtr {
1175 fn event(
1176 this: &mut Self,
1177 _: &wp_fractional_scale_v1::WpFractionalScaleV1,
1178 event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
1179 surface_id: &ObjectId,
1180 _: &Connection,
1181 _: &QueueHandle<Self>,
1182 ) {
1183 let client = this.get_client();
1184 let mut state = client.borrow_mut();
1185
1186 let Some(window) = get_window(&mut state, surface_id) else {
1187 return;
1188 };
1189
1190 drop(state);
1191 window.handle_fractional_scale_event(event);
1192 }
1193}
1194
1195impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
1196 for WaylandClientStatePtr
1197{
1198 fn event(
1199 this: &mut Self,
1200 _: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
1201 event: zxdg_toplevel_decoration_v1::Event,
1202 surface_id: &ObjectId,
1203 _: &Connection,
1204 _: &QueueHandle<Self>,
1205 ) {
1206 let client = this.get_client();
1207 let mut state = client.borrow_mut();
1208 let Some(window) = get_window(&mut state, surface_id) else {
1209 return;
1210 };
1211
1212 drop(state);
1213 window.handle_toplevel_decoration_event(event);
1214 }
1215}
1216
1217const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
1218
1219impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
1220 fn event(
1221 this: &mut Self,
1222 _: &wl_data_device::WlDataDevice,
1223 event: wl_data_device::Event,
1224 _: &(),
1225 _: &Connection,
1226 _: &QueueHandle<Self>,
1227 ) {
1228 let client = this.get_client();
1229 let mut state = client.borrow_mut();
1230
1231 match event {
1232 wl_data_device::Event::Enter {
1233 serial,
1234 surface,
1235 x,
1236 y,
1237 id: data_offer,
1238 } => {
1239 state.serial = serial;
1240 if let Some(data_offer) = data_offer {
1241 let Some(drag_window) = get_window(&mut state, &surface.id()) else {
1242 return;
1243 };
1244
1245 const ACTIONS: DndAction = DndAction::Copy;
1246 data_offer.set_actions(ACTIONS, ACTIONS);
1247
1248 let pipe = Pipe::new().unwrap();
1249 data_offer.receive(FILE_LIST_MIME_TYPE.to_string(), unsafe {
1250 BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
1251 });
1252 let fd = pipe.read;
1253 drop(pipe.write);
1254
1255 let read_task = state
1256 .common
1257 .background_executor
1258 .spawn(async { unsafe { read_fd(fd) } });
1259
1260 let this = this.clone();
1261 state
1262 .common
1263 .foreground_executor
1264 .spawn(async move {
1265 let file_list = match read_task.await {
1266 Ok(list) => list,
1267 Err(err) => {
1268 log::error!("error reading drag and drop pipe: {err:?}");
1269 return;
1270 }
1271 };
1272
1273 let paths: SmallVec<[_; 2]> = file_list
1274 .lines()
1275 .map(|path| PathBuf::from(path.replace("file://", "")))
1276 .collect();
1277 let position = Point::new(x.into(), y.into());
1278
1279 // Prevent dropping text from other programs.
1280 if paths.is_empty() {
1281 data_offer.finish();
1282 data_offer.destroy();
1283 return;
1284 }
1285
1286 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
1287 position,
1288 paths: crate::ExternalPaths(paths),
1289 });
1290
1291 let client = this.get_client();
1292 let mut state = client.borrow_mut();
1293 state.drag.data_offer = Some(data_offer);
1294 state.drag.window = Some(drag_window.clone());
1295 state.drag.position = position;
1296
1297 drop(state);
1298 drag_window.handle_input(input);
1299 })
1300 .detach();
1301 }
1302 }
1303 wl_data_device::Event::Motion { x, y, .. } => {
1304 let Some(drag_window) = state.drag.window.clone() else {
1305 return;
1306 };
1307 let position = Point::new(x.into(), y.into());
1308 state.drag.position = position;
1309
1310 let input = PlatformInput::FileDrop(FileDropEvent::Pending { position });
1311 drop(state);
1312 drag_window.handle_input(input);
1313 }
1314 wl_data_device::Event::Leave => {
1315 let Some(drag_window) = state.drag.window.clone() else {
1316 return;
1317 };
1318 let data_offer = state.drag.data_offer.clone().unwrap();
1319 data_offer.destroy();
1320
1321 state.drag.data_offer = None;
1322 state.drag.window = None;
1323
1324 let input = PlatformInput::FileDrop(FileDropEvent::Exited {});
1325 drop(state);
1326 drag_window.handle_input(input);
1327 }
1328 wl_data_device::Event::Drop => {
1329 let Some(drag_window) = state.drag.window.clone() else {
1330 return;
1331 };
1332 let data_offer = state.drag.data_offer.clone().unwrap();
1333 data_offer.finish();
1334 data_offer.destroy();
1335
1336 state.drag.data_offer = None;
1337 state.drag.window = None;
1338
1339 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
1340 position: state.drag.position,
1341 });
1342 drop(state);
1343 drag_window.handle_input(input);
1344 }
1345 _ => {}
1346 }
1347 }
1348
1349 event_created_child!(WaylandClientStatePtr, wl_data_device::WlDataDevice, [
1350 wl_data_device::EVT_DATA_OFFER_OPCODE => (wl_data_offer::WlDataOffer, ()),
1351 ]);
1352}
1353
1354impl Dispatch<wl_data_offer::WlDataOffer, ()> for WaylandClientStatePtr {
1355 fn event(
1356 this: &mut Self,
1357 data_offer: &wl_data_offer::WlDataOffer,
1358 event: wl_data_offer::Event,
1359 _: &(),
1360 _: &Connection,
1361 _: &QueueHandle<Self>,
1362 ) {
1363 let client = this.get_client();
1364 let mut state = client.borrow_mut();
1365
1366 match event {
1367 wl_data_offer::Event::Offer { mime_type } => {
1368 if mime_type == FILE_LIST_MIME_TYPE {
1369 data_offer.accept(state.serial, Some(mime_type));
1370 }
1371 }
1372 _ => {}
1373 }
1374 }
1375}