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