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