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