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