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