1use std::cell::RefCell;
2use std::mem;
3use std::num::NonZeroU32;
4use std::rc::Rc;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8use calloop::timer::{TimeoutAction, Timer};
9use calloop::LoopHandle;
10use calloop_wayland_source::WaylandSource;
11use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
12use copypasta::ClipboardProvider;
13use wayland_backend::client::ObjectId;
14use wayland_backend::protocol::WEnum;
15use wayland_client::globals::{registry_queue_init, GlobalListContents};
16use wayland_client::protocol::wl_callback::WlCallback;
17use wayland_client::protocol::wl_output;
18use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
19use wayland_client::{
20 delegate_noop,
21 protocol::{
22 wl_buffer, wl_callback, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat,
23 wl_shm, wl_shm_pool,
24 wl_surface::{self, WlSurface},
25 },
26 Connection, Dispatch, Proxy, QueueHandle,
27};
28use wayland_protocols::wp::fractional_scale::v1::client::{
29 wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
30};
31use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
32use wayland_protocols::xdg::decoration::zv1::client::{
33 zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
34};
35use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
36use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
37use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
38
39use super::super::DOUBLE_CLICK_INTERVAL;
40use crate::platform::linux::client::Client;
41use crate::platform::linux::util::is_within_click_distance;
42use crate::platform::linux::wayland::cursor::Cursor;
43use crate::platform::linux::wayland::window::{WaylandDecorationState, WaylandWindow};
44use crate::platform::{LinuxPlatformInner, PlatformWindow};
45use crate::WindowParams;
46use crate::{
47 platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId,
48 KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
49 MouseDownEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
50 PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase,
51};
52use crate::{point, px};
53
54/// Used to convert evdev scancode to xkb scancode
55const MIN_KEYCODE: u32 = 8;
56
57pub(crate) struct WaylandClientStateInner {
58 compositor: wl_compositor::WlCompositor,
59 wm_base: xdg_wm_base::XdgWmBase,
60 shm: wl_shm::WlShm,
61 viewporter: Option<wp_viewporter::WpViewporter>,
62 fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
63 decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
64 windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
65 outputs: Vec<(wl_output::WlOutput, Rc<RefCell<OutputState>>)>,
66 platform_inner: Rc<LinuxPlatformInner>,
67 keymap_state: Option<xkb::State>,
68 click_state: ClickState,
69 repeat: KeyRepeat,
70 modifiers: Modifiers,
71 scroll_direction: f64,
72 axis_source: AxisSource,
73 mouse_location: Point<Pixels>,
74 button_pressed: Option<MouseButton>,
75 mouse_focused_window: Option<Rc<WaylandWindowState>>,
76 keyboard_focused_window: Option<Rc<WaylandWindowState>>,
77 loop_handle: Rc<LoopHandle<'static, ()>>,
78}
79
80pub(crate) struct CursorState {
81 cursor_icon_name: String,
82 cursor: Option<Cursor>,
83}
84
85#[derive(Clone)]
86pub(crate) struct WaylandClientState {
87 client_state_inner: Rc<RefCell<WaylandClientStateInner>>,
88 cursor_state: Rc<RefCell<CursorState>>,
89 clipboard: Rc<RefCell<Clipboard>>,
90 primary: Rc<RefCell<Primary>>,
91}
92
93struct ClickState {
94 last_click: Instant,
95 last_location: Point<Pixels>,
96 current_count: usize,
97}
98
99pub(crate) struct KeyRepeat {
100 characters_per_second: u32,
101 delay: Duration,
102 current_id: u64,
103 current_keysym: Option<xkb::Keysym>,
104}
105
106pub(crate) struct WaylandClient {
107 platform_inner: Rc<LinuxPlatformInner>,
108 state: WaylandClientState,
109 qh: Arc<QueueHandle<WaylandClientState>>,
110}
111
112const WL_SEAT_MIN_VERSION: u32 = 4;
113const WL_OUTPUT_VERSION: u32 = 2;
114
115fn wl_seat_version(version: u32) -> u32 {
116 if version >= wl_pointer::EVT_AXIS_VALUE120_SINCE {
117 wl_pointer::EVT_AXIS_VALUE120_SINCE
118 } else if version >= WL_SEAT_MIN_VERSION {
119 WL_SEAT_MIN_VERSION
120 } else {
121 panic!(
122 "wl_seat below required version: {} < {}",
123 version, WL_SEAT_MIN_VERSION
124 );
125 }
126}
127
128impl WaylandClient {
129 pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
130 let conn = Connection::connect_to_env().unwrap();
131
132 let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
133 let qh = event_queue.handle();
134 let mut outputs = Vec::new();
135
136 globals.contents().with_list(|list| {
137 for global in list {
138 match &global.interface[..] {
139 "wl_seat" => {
140 globals.registry().bind::<wl_seat::WlSeat, _, _>(
141 global.name,
142 wl_seat_version(global.version),
143 &qh,
144 (),
145 );
146 }
147 "wl_output" => outputs.push((
148 globals.registry().bind::<wl_output::WlOutput, _, _>(
149 global.name,
150 WL_OUTPUT_VERSION,
151 &qh,
152 (),
153 ),
154 Rc::new(RefCell::new(OutputState::default())),
155 )),
156 _ => {}
157 }
158 }
159 });
160
161 let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
162 let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
163
164 let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner {
165 compositor: globals
166 .bind(&qh, 1..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE, ())
167 .unwrap(),
168 wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
169 shm: globals.bind(&qh, 1..=1, ()).unwrap(),
170 outputs,
171 viewporter: globals.bind(&qh, 1..=1, ()).ok(),
172 fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
173 decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
174 windows: Vec::new(),
175 platform_inner: Rc::clone(&linux_platform_inner),
176 keymap_state: None,
177 click_state: ClickState {
178 last_click: Instant::now(),
179 last_location: Point::new(px(0.0), px(0.0)),
180 current_count: 0,
181 },
182 repeat: KeyRepeat {
183 characters_per_second: 16,
184 delay: Duration::from_millis(500),
185 current_id: 0,
186 current_keysym: None,
187 },
188 modifiers: Modifiers {
189 shift: false,
190 control: false,
191 alt: false,
192 function: false,
193 platform: false,
194 },
195 scroll_direction: -1.0,
196 axis_source: AxisSource::Wheel,
197 mouse_location: point(px(0.0), px(0.0)),
198 button_pressed: None,
199 mouse_focused_window: None,
200 keyboard_focused_window: None,
201 loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
202 }));
203
204 let mut cursor_state = Rc::new(RefCell::new(CursorState {
205 cursor_icon_name: "arrow".to_string(),
206 cursor: None,
207 }));
208
209 let source = WaylandSource::new(conn, event_queue);
210
211 let mut state = WaylandClientState {
212 client_state_inner: Rc::clone(&state_inner),
213 cursor_state: Rc::clone(&cursor_state),
214 clipboard: Rc::new(RefCell::new(clipboard)),
215 primary: Rc::new(RefCell::new(primary)),
216 };
217 let mut state_loop = state.clone();
218 linux_platform_inner
219 .loop_handle
220 .insert_source(source, move |_, queue, _| {
221 queue.dispatch_pending(&mut state_loop)
222 })
223 .unwrap();
224
225 Self {
226 platform_inner: linux_platform_inner,
227 state,
228 qh: Arc::new(qh),
229 }
230 }
231}
232
233impl Client for WaylandClient {
234 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
235 Vec::new()
236 }
237
238 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
239 unimplemented!()
240 }
241
242 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
243 None
244 }
245
246 fn open_window(
247 &self,
248 handle: AnyWindowHandle,
249 options: WindowParams,
250 ) -> Box<dyn PlatformWindow> {
251 let mut state = self.state.client_state_inner.borrow_mut();
252
253 let wl_surface = state.compositor.create_surface(&self.qh, ());
254 let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
255 let toplevel = xdg_surface.get_toplevel(&self.qh, ());
256 let wl_surface = Arc::new(wl_surface);
257
258 // Attempt to set up window decorations based on the requested configuration
259 //
260 // Note that wayland compositors may either not support decorations at all, or may
261 // support them but not allow clients to choose whether they are enabled or not.
262 // We attempt to account for these cases here.
263
264 if let Some(decoration_manager) = state.decoration_manager.as_ref() {
265 // The protocol for managing decorations is present at least, but that doesn't
266 // mean that the compositor will allow us to use it.
267
268 let decoration =
269 decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, xdg_surface.id());
270
271 // todo(linux) - options.titlebar is lacking information required for wayland.
272 // Especially, whether a titlebar is wanted in itself.
273 //
274 // Removing the titlebar also removes the entire window frame (ie. the ability to
275 // close, move and resize the window [snapping still works]). This needs additional
276 // handling in Zed, in order to implement drag handlers on a titlebar element.
277 //
278 // Since all of this handling is not present, we request server-side decorations
279 // for now as a stopgap solution.
280 decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
281 }
282
283 let viewport = state
284 .viewporter
285 .as_ref()
286 .map(|viewporter| viewporter.get_viewport(&wl_surface, &self.qh, ()));
287
288 wl_surface.frame(&self.qh, wl_surface.clone());
289 wl_surface.commit();
290
291 let window_state: Rc<WaylandWindowState> = Rc::new(WaylandWindowState::new(
292 wl_surface.clone(),
293 viewport,
294 Arc::new(toplevel),
295 options,
296 ));
297
298 if let Some(fractional_scale_manager) = state.fractional_scale_manager.as_ref() {
299 fractional_scale_manager.get_fractional_scale(&wl_surface, &self.qh, xdg_surface.id());
300 }
301
302 state.windows.push((xdg_surface, Rc::clone(&window_state)));
303 Box::new(WaylandWindow(window_state))
304 }
305
306 fn set_cursor_style(&self, style: CursorStyle) {
307 // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
308 // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
309 // Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values
310 let cursor_icon_name = match style {
311 CursorStyle::Arrow => "arrow",
312 CursorStyle::IBeam => "text",
313 CursorStyle::Crosshair => "crosshair",
314 CursorStyle::ClosedHand => "grabbing",
315 CursorStyle::OpenHand => "grab",
316 CursorStyle::PointingHand => "pointer",
317 CursorStyle::ResizeLeft => "w-resize",
318 CursorStyle::ResizeRight => "e-resize",
319 CursorStyle::ResizeLeftRight => "ew-resize",
320 CursorStyle::ResizeUp => "n-resize",
321 CursorStyle::ResizeDown => "s-resize",
322 CursorStyle::ResizeUpDown => "ns-resize",
323 CursorStyle::DisappearingItem => "grabbing", // todo(linux) - couldn't find equivalent icon in linux
324 CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
325 CursorStyle::OperationNotAllowed => "not-allowed",
326 CursorStyle::DragLink => "alias",
327 CursorStyle::DragCopy => "copy",
328 CursorStyle::ContextualMenu => "context-menu",
329 }
330 .to_string();
331
332 self.state.cursor_state.borrow_mut().cursor_icon_name = cursor_icon_name;
333 }
334
335 fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
336 self.state.clipboard.clone()
337 }
338
339 fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
340 self.state.primary.clone()
341 }
342}
343
344impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
345 fn event(
346 state: &mut Self,
347 registry: &wl_registry::WlRegistry,
348 event: wl_registry::Event,
349 _: &GlobalListContents,
350 _: &Connection,
351 qh: &QueueHandle<Self>,
352 ) {
353 let mut state = state.client_state_inner.borrow_mut();
354 match event {
355 wl_registry::Event::Global {
356 name,
357 interface,
358 version,
359 } => match &interface[..] {
360 "wl_seat" => {
361 registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
362 }
363 "wl_output" => {
364 state.outputs.push((
365 registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ()),
366 Rc::new(RefCell::new(OutputState::default())),
367 ));
368 }
369 _ => {}
370 },
371 wl_registry::Event::GlobalRemove { name: _ } => {}
372 _ => {}
373 }
374 }
375}
376
377delegate_noop!(WaylandClientState: ignore wl_compositor::WlCompositor);
378delegate_noop!(WaylandClientState: ignore wl_shm::WlShm);
379delegate_noop!(WaylandClientState: ignore wl_shm_pool::WlShmPool);
380delegate_noop!(WaylandClientState: ignore wl_buffer::WlBuffer);
381delegate_noop!(WaylandClientState: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
382delegate_noop!(WaylandClientState: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
383delegate_noop!(WaylandClientState: ignore wp_viewporter::WpViewporter);
384delegate_noop!(WaylandClientState: ignore wp_viewport::WpViewport);
385
386impl Dispatch<WlCallback, Arc<WlSurface>> for WaylandClientState {
387 fn event(
388 state: &mut Self,
389 _: &WlCallback,
390 event: wl_callback::Event,
391 surf: &Arc<WlSurface>,
392 _: &Connection,
393 qh: &QueueHandle<Self>,
394 ) {
395 let mut state = state.client_state_inner.borrow_mut();
396 if let wl_callback::Event::Done { .. } = event {
397 for window in &state.windows {
398 if window.1.surface.id() == surf.id() {
399 window.1.surface.frame(qh, surf.clone());
400 window.1.update();
401 window.1.surface.commit();
402 }
403 }
404 }
405 }
406}
407
408impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientState {
409 fn event(
410 state: &mut Self,
411 surface: &wl_surface::WlSurface,
412 event: <wl_surface::WlSurface as Proxy>::Event,
413 _: &(),
414 _: &Connection,
415 _: &QueueHandle<Self>,
416 ) {
417 let mut state = state.client_state_inner.borrow_mut();
418
419 // We use `WpFractionalScale` instead to set the scale if it's available
420 // or give up on scaling if `WlSurface::set_buffer_scale` isn't available
421 if state.fractional_scale_manager.is_some()
422 || state.compositor.version() < wl_surface::REQ_SET_BUFFER_SCALE_SINCE
423 {
424 return;
425 }
426
427 let Some(window) = state
428 .windows
429 .iter()
430 .map(|(_, state)| state)
431 .find(|state| &*state.surface == surface)
432 else {
433 return;
434 };
435
436 let mut outputs = window.outputs.borrow_mut();
437
438 match event {
439 wl_surface::Event::Enter { output } => {
440 // We use `PreferredBufferScale` instead to set the scale if it's available
441 if surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
442 return;
443 }
444 let mut scale = 1;
445 for global_output in &state.outputs {
446 if output == global_output.0 {
447 outputs.insert(output.id());
448 scale = scale.max(global_output.1.borrow().scale.get());
449 } else if outputs.contains(&global_output.0.id()) {
450 scale = scale.max(global_output.1.borrow().scale.get());
451 }
452 }
453 window.rescale(scale as f32);
454 window.surface.set_buffer_scale(scale as i32);
455 }
456 wl_surface::Event::Leave { output } => {
457 // We use `PreferredBufferScale` instead to set the scale if it's available
458 if surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
459 return;
460 }
461
462 outputs.remove(&output.id());
463
464 let mut scale = 1;
465 for global_output in &state.outputs {
466 if outputs.contains(&global_output.0.id()) {
467 scale = scale.max(global_output.1.borrow().scale.get());
468 }
469 }
470 window.rescale(scale as f32);
471 window.surface.set_buffer_scale(scale as i32);
472 }
473 wl_surface::Event::PreferredBufferScale { factor } => {
474 window.rescale(factor as f32);
475 surface.set_buffer_scale(factor);
476 }
477 _ => {}
478 }
479 }
480}
481
482#[derive(Debug, Clone)]
483struct OutputState {
484 scale: NonZeroU32,
485}
486
487impl Default for OutputState {
488 fn default() -> Self {
489 Self {
490 scale: NonZeroU32::new(1).unwrap(),
491 }
492 }
493}
494
495impl Dispatch<wl_output::WlOutput, ()> for WaylandClientState {
496 fn event(
497 state: &mut Self,
498 output: &wl_output::WlOutput,
499 event: <wl_output::WlOutput as Proxy>::Event,
500 _: &(),
501 _: &Connection,
502 _: &QueueHandle<Self>,
503 ) {
504 let mut state = state.client_state_inner.borrow_mut();
505 let mut output_state = state
506 .outputs
507 .iter_mut()
508 .find(|(o, _)| o == output)
509 .map(|(_, state)| state)
510 .unwrap()
511 .borrow_mut();
512 match event {
513 wl_output::Event::Scale { factor } => {
514 if factor > 0 {
515 output_state.scale = NonZeroU32::new(factor as u32).unwrap();
516 }
517 }
518 _ => {}
519 }
520 }
521}
522
523impl Dispatch<xdg_surface::XdgSurface, ()> for WaylandClientState {
524 fn event(
525 state: &mut Self,
526 xdg_surface: &xdg_surface::XdgSurface,
527 event: xdg_surface::Event,
528 _: &(),
529 _: &Connection,
530 _: &QueueHandle<Self>,
531 ) {
532 let mut state = state.client_state_inner.borrow_mut();
533 if let xdg_surface::Event::Configure { serial, .. } = event {
534 xdg_surface.ack_configure(serial);
535 for window in &state.windows {
536 if &window.0 == xdg_surface {
537 window.1.update();
538 window.1.surface.commit();
539 return;
540 }
541 }
542 }
543 }
544}
545
546impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
547 fn event(
548 state: &mut Self,
549 xdg_toplevel: &xdg_toplevel::XdgToplevel,
550 event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
551 _: &(),
552 _: &Connection,
553 _: &QueueHandle<Self>,
554 ) {
555 let mut state = state.client_state_inner.borrow_mut();
556 if let xdg_toplevel::Event::Configure {
557 width,
558 height,
559 states,
560 } = event
561 {
562 let width = NonZeroU32::new(width as u32);
563 let height = NonZeroU32::new(height as u32);
564 let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
565 for window in &state.windows {
566 if window.1.toplevel.id() == xdg_toplevel.id() {
567 window.1.resize(width, height);
568 window.1.set_fullscreen(fullscreen);
569 window.1.surface.commit();
570 return;
571 }
572 }
573 } else if let xdg_toplevel::Event::Close = event {
574 state.windows.retain(|(_, window)| {
575 if window.toplevel.id() == xdg_toplevel.id() {
576 window.toplevel.destroy();
577 false
578 } else {
579 true
580 }
581 });
582 state.platform_inner.loop_signal.stop();
583 }
584 }
585}
586
587impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientState {
588 fn event(
589 _: &mut Self,
590 wm_base: &xdg_wm_base::XdgWmBase,
591 event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
592 _: &(),
593 _: &Connection,
594 _: &QueueHandle<Self>,
595 ) {
596 if let xdg_wm_base::Event::Ping { serial } = event {
597 wm_base.pong(serial);
598 }
599 }
600}
601
602impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientState {
603 fn event(
604 state: &mut Self,
605 seat: &wl_seat::WlSeat,
606 event: wl_seat::Event,
607 data: &(),
608 conn: &Connection,
609 qh: &QueueHandle<Self>,
610 ) {
611 if let wl_seat::Event::Capabilities {
612 capabilities: WEnum::Value(capabilities),
613 } = event
614 {
615 if capabilities.contains(wl_seat::Capability::Keyboard) {
616 seat.get_keyboard(qh, ());
617 }
618 if capabilities.contains(wl_seat::Capability::Pointer) {
619 seat.get_pointer(qh, ());
620 }
621 }
622 }
623}
624
625impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
626 fn event(
627 this: &mut Self,
628 keyboard: &wl_keyboard::WlKeyboard,
629 event: wl_keyboard::Event,
630 data: &(),
631 conn: &Connection,
632 qh: &QueueHandle<Self>,
633 ) {
634 let mut state = this.client_state_inner.borrow_mut();
635 match event {
636 wl_keyboard::Event::RepeatInfo { rate, delay } => {
637 state.repeat.characters_per_second = rate as u32;
638 state.repeat.delay = Duration::from_millis(delay as u64);
639 }
640 wl_keyboard::Event::Keymap {
641 format: WEnum::Value(format),
642 fd,
643 size,
644 ..
645 } => {
646 assert_eq!(
647 format,
648 wl_keyboard::KeymapFormat::XkbV1,
649 "Unsupported keymap format"
650 );
651 let keymap = unsafe {
652 xkb::Keymap::new_from_fd(
653 &xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
654 fd,
655 size as usize,
656 XKB_KEYMAP_FORMAT_TEXT_V1,
657 KEYMAP_COMPILE_NO_FLAGS,
658 )
659 .unwrap()
660 }
661 .unwrap();
662 state.keymap_state = Some(xkb::State::new(&keymap));
663 }
664 wl_keyboard::Event::Enter { surface, .. } => {
665 state.keyboard_focused_window = state
666 .windows
667 .iter()
668 .find(|&w| w.1.surface.id() == surface.id())
669 .map(|w| w.1.clone());
670
671 if let Some(window) = &state.keyboard_focused_window {
672 window.set_focused(true);
673 }
674 }
675 wl_keyboard::Event::Leave { surface, .. } => {
676 let keyboard_focused_window = state
677 .windows
678 .iter()
679 .find(|&w| w.1.surface.id() == surface.id())
680 .map(|w| w.1.clone());
681
682 if let Some(window) = keyboard_focused_window {
683 window.set_focused(false);
684 }
685
686 state.keyboard_focused_window = None;
687 }
688 wl_keyboard::Event::Modifiers {
689 mods_depressed,
690 mods_latched,
691 mods_locked,
692 group,
693 ..
694 } => {
695 let focused_window = state.keyboard_focused_window.clone();
696 let Some(focused_window) = focused_window else {
697 return;
698 };
699
700 let keymap_state = state.keymap_state.as_mut().unwrap();
701 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
702
703 let shift =
704 keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
705 let alt =
706 keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
707 let control =
708 keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
709 let command =
710 keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
711
712 state.modifiers.shift = shift;
713 state.modifiers.alt = alt;
714 state.modifiers.control = control;
715 state.modifiers.platform = command;
716
717 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
718 modifiers: state.modifiers,
719 });
720
721 drop(state);
722
723 focused_window.handle_input(input);
724 }
725 wl_keyboard::Event::Key {
726 key,
727 state: WEnum::Value(key_state),
728 ..
729 } => {
730 let focused_window = state.keyboard_focused_window.clone();
731 let Some(focused_window) = focused_window else {
732 return;
733 };
734 let focused_window = focused_window.clone();
735
736 let keymap_state = state.keymap_state.as_ref().unwrap();
737 let keycode = Keycode::from(key + MIN_KEYCODE);
738 let keysym = keymap_state.key_get_one_sym(keycode);
739
740 match key_state {
741 wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
742 let input = PlatformInput::KeyDown(KeyDownEvent {
743 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
744 is_held: false, // todo(linux)
745 });
746
747 state.repeat.current_id += 1;
748 state.repeat.current_keysym = Some(keysym);
749
750 let rate = state.repeat.characters_per_second;
751 let delay = state.repeat.delay;
752 let id = state.repeat.current_id;
753 let this = this.clone();
754
755 let timer = Timer::from_duration(delay);
756 let state_ = Rc::clone(&this.client_state_inner);
757 let input_ = input.clone();
758 state
759 .loop_handle
760 .insert_source(timer, move |event, _metadata, shared_data| {
761 let state_ = state_.borrow_mut();
762 let is_repeating = id == state_.repeat.current_id
763 && state_.repeat.current_keysym.is_some()
764 && state_.keyboard_focused_window.is_some();
765
766 if !is_repeating {
767 return TimeoutAction::Drop;
768 }
769
770 let focused_window =
771 state_.keyboard_focused_window.as_ref().unwrap().clone();
772
773 drop(state_);
774
775 focused_window.handle_input(input_.clone());
776
777 TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
778 })
779 .unwrap();
780
781 drop(state);
782
783 focused_window.handle_input(input);
784 }
785 wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
786 let input = PlatformInput::KeyUp(KeyUpEvent {
787 keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
788 });
789
790 state.repeat.current_keysym = None;
791
792 drop(state);
793
794 focused_window.handle_input(input);
795 }
796 _ => {}
797 }
798 }
799 _ => {}
800 }
801 }
802}
803
804fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
805 // These values are coming from <linux/input-event-codes.h>.
806 const BTN_LEFT: u32 = 0x110;
807 const BTN_RIGHT: u32 = 0x111;
808 const BTN_MIDDLE: u32 = 0x112;
809 const BTN_SIDE: u32 = 0x113;
810 const BTN_EXTRA: u32 = 0x114;
811 const BTN_FORWARD: u32 = 0x115;
812 const BTN_BACK: u32 = 0x116;
813
814 Some(match button {
815 BTN_LEFT => MouseButton::Left,
816 BTN_RIGHT => MouseButton::Right,
817 BTN_MIDDLE => MouseButton::Middle,
818 BTN_BACK | BTN_SIDE => MouseButton::Navigate(NavigationDirection::Back),
819 BTN_FORWARD | BTN_EXTRA => MouseButton::Navigate(NavigationDirection::Forward),
820 _ => return None,
821 })
822}
823
824impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
825 fn event(
826 self_state: &mut Self,
827 wl_pointer: &wl_pointer::WlPointer,
828 event: wl_pointer::Event,
829 data: &(),
830 conn: &Connection,
831 qh: &QueueHandle<Self>,
832 ) {
833 let mut state = self_state.client_state_inner.borrow_mut();
834 {
835 let mut cursor_state = self_state.cursor_state.borrow_mut();
836 if cursor_state.cursor.is_none() {
837 cursor_state.cursor =
838 Some(Cursor::new(&conn, &state.compositor, &qh, &state.shm, 24));
839 }
840 }
841
842 match event {
843 wl_pointer::Event::Enter {
844 serial,
845 surface,
846 surface_x,
847 surface_y,
848 ..
849 } => {
850 let windows = mem::take(&mut state.windows);
851 for window in windows.iter() {
852 if window.1.surface.id() == surface.id() {
853 window.1.set_focused(true);
854 state.mouse_focused_window = Some(window.1.clone());
855 let mut cursor_state = self_state.cursor_state.borrow_mut();
856 let cursor_icon_name = cursor_state.cursor_icon_name.clone();
857 if let Some(mut cursor) = cursor_state.cursor.as_mut() {
858 cursor.set_serial_id(serial);
859 cursor.set_icon(&wl_pointer, cursor_icon_name);
860 }
861 }
862 }
863 state.windows = windows;
864 state.mouse_location = point(px(surface_x as f32), px(surface_y as f32));
865 }
866 wl_pointer::Event::Motion {
867 time,
868 surface_x,
869 surface_y,
870 ..
871 } => {
872 if state.mouse_focused_window.is_none() {
873 return;
874 }
875 state.mouse_location = point(px(surface_x as f32), px(surface_y as f32));
876
877 state.mouse_focused_window.as_ref().unwrap().handle_input(
878 PlatformInput::MouseMove(MouseMoveEvent {
879 position: state.mouse_location,
880 pressed_button: state.button_pressed,
881 modifiers: state.modifiers,
882 }),
883 );
884 let mut cursor_state = self_state.cursor_state.borrow_mut();
885 let cursor_icon_name = cursor_state.cursor_icon_name.clone();
886 if let Some(mut cursor) = cursor_state.cursor.as_mut() {
887 cursor.set_icon(&wl_pointer, cursor_icon_name);
888 }
889 }
890 wl_pointer::Event::Button {
891 button,
892 state: WEnum::Value(button_state),
893 ..
894 } => {
895 let button = linux_button_to_gpui(button);
896 let Some(button) = button else { return };
897 if state.mouse_focused_window.is_none() {
898 return;
899 }
900 match button_state {
901 wl_pointer::ButtonState::Pressed => {
902 let click_elapsed = state.click_state.last_click.elapsed();
903
904 if click_elapsed < DOUBLE_CLICK_INTERVAL
905 && is_within_click_distance(
906 state.click_state.last_location,
907 state.mouse_location,
908 )
909 {
910 state.click_state.current_count += 1;
911 } else {
912 state.click_state.current_count = 1;
913 }
914
915 state.click_state.last_click = Instant::now();
916 state.click_state.last_location = state.mouse_location;
917
918 state.button_pressed = Some(button);
919 state.mouse_focused_window.as_ref().unwrap().handle_input(
920 PlatformInput::MouseDown(MouseDownEvent {
921 button,
922 position: state.mouse_location,
923 modifiers: state.modifiers,
924 click_count: state.click_state.current_count,
925 first_mouse: false,
926 }),
927 );
928 }
929 wl_pointer::ButtonState::Released => {
930 state.button_pressed = None;
931 state.mouse_focused_window.as_ref().unwrap().handle_input(
932 PlatformInput::MouseUp(MouseUpEvent {
933 button,
934 position: state.mouse_location,
935 modifiers: state.modifiers,
936 click_count: state.click_state.current_count,
937 }),
938 );
939 }
940 _ => {}
941 }
942 }
943 wl_pointer::Event::AxisRelativeDirection {
944 direction: WEnum::Value(direction),
945 ..
946 } => {
947 state.scroll_direction = match direction {
948 AxisRelativeDirection::Identical => -1.0,
949 AxisRelativeDirection::Inverted => 1.0,
950 _ => -1.0,
951 }
952 }
953 wl_pointer::Event::AxisSource {
954 axis_source: WEnum::Value(axis_source),
955 } => {
956 state.axis_source = axis_source;
957 }
958 wl_pointer::Event::AxisValue120 {
959 axis: WEnum::Value(axis),
960 value120,
961 } => {
962 if let Some(focused_window) = &state.mouse_focused_window {
963 let value = value120 as f64 * state.scroll_direction;
964 focused_window.handle_input(PlatformInput::ScrollWheel(ScrollWheelEvent {
965 position: state.mouse_location,
966 delta: match axis {
967 wl_pointer::Axis::VerticalScroll => {
968 ScrollDelta::Pixels(point(px(0.0), px(value as f32)))
969 }
970 wl_pointer::Axis::HorizontalScroll => {
971 ScrollDelta::Pixels(point(px(value as f32), px(0.0)))
972 }
973 _ => unimplemented!(),
974 },
975 modifiers: state.modifiers,
976 touch_phase: TouchPhase::Moved,
977 }))
978 }
979 }
980 wl_pointer::Event::Axis {
981 time,
982 axis: WEnum::Value(axis),
983 value,
984 ..
985 } => {
986 // We handle discrete scroll events with `AxisValue120`.
987 if wl_pointer.version() >= wl_pointer::EVT_AXIS_VALUE120_SINCE
988 && state.axis_source == AxisSource::Wheel
989 {
990 return;
991 }
992 if let Some(focused_window) = &state.mouse_focused_window {
993 let value = value * state.scroll_direction;
994 focused_window.handle_input(PlatformInput::ScrollWheel(ScrollWheelEvent {
995 position: state.mouse_location,
996 delta: match axis {
997 wl_pointer::Axis::VerticalScroll => {
998 ScrollDelta::Pixels(point(px(0.0), px(value as f32)))
999 }
1000 wl_pointer::Axis::HorizontalScroll => {
1001 ScrollDelta::Pixels(point(px(value as f32), px(0.0)))
1002 }
1003 _ => unimplemented!(),
1004 },
1005 modifiers: state.modifiers,
1006 touch_phase: TouchPhase::Moved,
1007 }))
1008 }
1009 }
1010 wl_pointer::Event::Leave { surface, .. } => {
1011 if let Some(focused_window) = &state.mouse_focused_window {
1012 focused_window.handle_input(PlatformInput::MouseMove(MouseMoveEvent {
1013 position: Point::default(),
1014 pressed_button: None,
1015 modifiers: Modifiers::default(),
1016 }));
1017 focused_window.set_focused(false);
1018 }
1019 state.mouse_focused_window = None;
1020 }
1021 _ => {}
1022 }
1023 }
1024}
1025
1026impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientState {
1027 fn event(
1028 state: &mut Self,
1029 _: &wp_fractional_scale_v1::WpFractionalScaleV1,
1030 event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
1031 id: &ObjectId,
1032 _: &Connection,
1033 _: &QueueHandle<Self>,
1034 ) {
1035 let mut state = state.client_state_inner.borrow_mut();
1036 if let wp_fractional_scale_v1::Event::PreferredScale { scale, .. } = event {
1037 for window in &state.windows {
1038 if window.0.id() == *id {
1039 window.1.rescale(scale as f32 / 120.0);
1040 return;
1041 }
1042 }
1043 }
1044 }
1045}
1046
1047impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
1048 for WaylandClientState
1049{
1050 fn event(
1051 state: &mut Self,
1052 _: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
1053 event: zxdg_toplevel_decoration_v1::Event,
1054 surface_id: &ObjectId,
1055 _: &Connection,
1056 _: &QueueHandle<Self>,
1057 ) {
1058 let mut state = state.client_state_inner.borrow_mut();
1059 if let zxdg_toplevel_decoration_v1::Event::Configure { mode, .. } = event {
1060 for window in &state.windows {
1061 if window.0.id() == *surface_id {
1062 match mode {
1063 WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
1064 window
1065 .1
1066 .set_decoration_state(WaylandDecorationState::Server);
1067 }
1068 WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
1069 window
1070 .1
1071 .set_decoration_state(WaylandDecorationState::Client);
1072 }
1073 WEnum::Value(_) => {
1074 log::warn!("Unknown decoration mode");
1075 }
1076 WEnum::Unknown(v) => {
1077 log::warn!("Unknown decoration mode: {}", v);
1078 }
1079 }
1080 return;
1081 }
1082 }
1083 }
1084 }
1085}