client.rs

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