Skip to repository content2840 lines · 107.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:03:43.034Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
client.rs
1use std::{
2 cell::{RefCell, RefMut},
3 hash::Hash,
4 os::fd::{AsRawFd, BorrowedFd},
5 path::PathBuf,
6 rc::{Rc, Weak},
7 time::{Duration, Instant},
8};
9
10use ashpd::WindowIdentifier;
11use calloop::{
12 EventLoop, LoopHandle,
13 timer::{TimeoutAction, Timer},
14};
15use calloop_wayland_source::WaylandSource;
16use collections::HashMap;
17use filedescriptor::Pipe;
18use gpui_util::ResultExt as _;
19use http_client::Url;
20use smallvec::SmallVec;
21use wayland_backend::client::ObjectId;
22use wayland_backend::protocol::WEnum;
23use wayland_client::event_created_child;
24use wayland_client::globals::{GlobalList, GlobalListContents, registry_queue_init};
25use wayland_client::protocol::wl_callback::{self, WlCallback};
26use wayland_client::protocol::wl_data_device_manager::DndAction;
27use wayland_client::protocol::wl_data_offer::WlDataOffer;
28use wayland_client::protocol::wl_pointer::AxisSource;
29use wayland_client::protocol::{
30 wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output, wl_region,
31};
32use wayland_client::{
33 Connection, Dispatch, Proxy, QueueHandle, delegate_noop,
34 protocol::{
35 wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,
36 wl_shm_pool, wl_surface,
37 },
38};
39use wayland_protocols::wp::pointer_gestures::zv1::client::{
40 zwp_pointer_gesture_pinch_v1, zwp_pointer_gestures_v1,
41};
42use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{
43 self, ZwpPrimarySelectionOfferV1,
44};
45use wayland_protocols::wp::primary_selection::zv1::client::{
46 zwp_primary_selection_device_manager_v1, zwp_primary_selection_device_v1,
47 zwp_primary_selection_source_v1,
48};
49use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
50 ContentHint, ContentPurpose,
51};
52use wayland_protocols::wp::text_input::zv3::client::{
53 zwp_text_input_manager_v3, zwp_text_input_v3,
54};
55use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
56use wayland_protocols::xdg::activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1};
57use wayland_protocols::xdg::decoration::zv1::client::{
58 zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
59};
60use wayland_protocols::xdg::shell::client::{
61 xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
62};
63use wayland_protocols::xdg::system_bell::v1::client::xdg_system_bell_v1;
64use wayland_protocols::{
65 wp::cursor_shape::v1::client::{wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1},
66 xdg::dialog::v1::client::xdg_wm_dialog_v1::{self, XdgWmDialogV1},
67};
68use wayland_protocols::{
69 wp::fractional_scale::v1::client::{wp_fractional_scale_manager_v1, wp_fractional_scale_v1},
70 xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1,
71};
72use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
73use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
74use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
75use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode};
76
77use super::{
78 display::WaylandDisplay,
79 window::{ImeInput, WaylandWindowStatePtr},
80};
81
82use crate::linux::{
83 DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, PIPE_READ_TIMEOUT,
84 SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,
85 is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,
86 modifiers_from_xkb, open_uri_internal, read_fd_with_timeout, reveal_path_internal,
87 wayland::{
88 clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
89 cursor::Cursor,
90 serial::{Serial, SerialKind, SerialTracker},
91 to_shape,
92 window::WaylandWindow,
93 },
94 xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
95};
96use gpui::{
97 AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
98 ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
99 MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
100 Pixels, PlatformDisplay, PlatformInput, PlatformKeyboardLayout, PlatformWindow, Point,
101 ScrollDelta, ScrollWheelEvent, SharedString, Size, TouchPhase, WindowButtonLayout, WindowKind,
102 WindowParams, point, profiler, px, size,
103};
104use gpui_wgpu::{CompositorGpuHint, GpuContext};
105use wayland_protocols::wp::linux_dmabuf::zv1::client::{
106 zwp_linux_dmabuf_feedback_v1, zwp_linux_dmabuf_v1,
107};
108
109/// Used to convert evdev scancode to xkb scancode
110const MIN_KEYCODE: u32 = 8;
111
112const UNKNOWN_KEYBOARD_LAYOUT_NAME: SharedString = SharedString::new_static("unknown");
113const XDG_ACTIVATION_TOKEN_ENV_VAR: &str = "XDG_ACTIVATION_TOKEN";
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116struct ImeCursorRectangle {
117 x: i32,
118 y: i32,
119 width: i32,
120 height: i32,
121}
122
123impl From<Bounds<Pixels>> for ImeCursorRectangle {
124 fn from(bounds: Bounds<Pixels>) -> Self {
125 Self {
126 x: bounds.origin.x.as_f32() as i32,
127 y: bounds.origin.y.as_f32() as i32,
128 width: bounds.size.width.as_f32() as i32,
129 height: bounds.size.height.as_f32() as i32,
130 }
131 }
132}
133
134trait ImeCursorRectangleSink {
135 fn set_ime_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32);
136 fn commit_ime_state(&self);
137}
138
139impl ImeCursorRectangleSink for zwp_text_input_v3::ZwpTextInputV3 {
140 fn set_ime_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32) {
141 self.set_cursor_rectangle(x, y, width, height);
142 }
143
144 fn commit_ime_state(&self) {
145 self.commit();
146 }
147}
148
149fn set_ime_cursor_rectangle(
150 text_input: &impl ImeCursorRectangleSink,
151 cursor_rectangle: ImeCursorRectangle,
152) {
153 text_input.set_ime_cursor_rectangle(
154 cursor_rectangle.x,
155 cursor_rectangle.y,
156 cursor_rectangle.width,
157 cursor_rectangle.height,
158 );
159}
160
161fn update_ime_cursor_rectangle(
162 text_input: &impl ImeCursorRectangleSink,
163 last_ime_cursor_rectangle: &mut Option<ImeCursorRectangle>,
164 bounds: Bounds<Pixels>,
165) {
166 let cursor_rectangle = ImeCursorRectangle::from(bounds);
167 if *last_ime_cursor_rectangle == Some(cursor_rectangle) {
168 return;
169 }
170
171 *last_ime_cursor_rectangle = Some(cursor_rectangle);
172 set_ime_cursor_rectangle(text_input, cursor_rectangle);
173 text_input.commit_ime_state();
174}
175
176fn set_ime_cursor_rectangle_after_done(
177 text_input: &impl ImeCursorRectangleSink,
178 last_ime_cursor_rectangle: &mut Option<ImeCursorRectangle>,
179 bounds: Bounds<Pixels>,
180 should_commit: bool,
181) {
182 if should_commit {
183 update_ime_cursor_rectangle(text_input, last_ime_cursor_rectangle, bounds);
184 } else {
185 set_ime_cursor_rectangle(text_input, ImeCursorRectangle::from(bounds));
186 }
187}
188
189fn take_startup_activation_token_from_environment() -> Option<String> {
190 let startup_activation_token = std::env::var(XDG_ACTIVATION_TOKEN_ENV_VAR)
191 .ok()
192 .filter(|token| !token.is_empty());
193 // The token must be removed from the environment so it isn't inherited by child
194 // processes we spawn, per the xdg-activation spec: https://wayland.app/protocols/xdg-activation-v1
195 // SAFETY: This runs during Wayland platform initialization before GPUI starts
196 // concurrent environment access or spawning child processes.
197 unsafe { std::env::remove_var(XDG_ACTIVATION_TOKEN_ENV_VAR) };
198 startup_activation_token
199}
200
201#[derive(Clone)]
202pub struct Globals {
203 pub qh: QueueHandle<WaylandClientStatePtr>,
204 pub activation: Option<xdg_activation_v1::XdgActivationV1>,
205 pub compositor: wl_compositor::WlCompositor,
206 pub cursor_shape_manager: Option<wp_cursor_shape_manager_v1::WpCursorShapeManagerV1>,
207 pub data_device_manager: Option<wl_data_device_manager::WlDataDeviceManager>,
208 pub primary_selection_manager:
209 Option<zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1>,
210 pub wm_base: xdg_wm_base::XdgWmBase,
211 pub shm: wl_shm::WlShm,
212 pub seat: wl_seat::WlSeat,
213 pub viewporter: Option<wp_viewporter::WpViewporter>,
214 pub fractional_scale_manager:
215 Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
216 pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
217 pub layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
218 pub blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>,
219 pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>,
220 pub gesture_manager: Option<zwp_pointer_gestures_v1::ZwpPointerGesturesV1>,
221 pub dialog: Option<xdg_wm_dialog_v1::XdgWmDialogV1>,
222 pub system_bell: Option<xdg_system_bell_v1::XdgSystemBellV1>,
223 pub executor: ForegroundExecutor,
224}
225
226impl Globals {
227 fn new(
228 globals: GlobalList,
229 executor: ForegroundExecutor,
230 qh: QueueHandle<WaylandClientStatePtr>,
231 seat: wl_seat::WlSeat,
232 ) -> Self {
233 let dialog_v = XdgWmDialogV1::interface().version;
234 Globals {
235 activation: globals.bind(&qh, 1..=1, ()).ok(),
236 compositor: globals
237 .bind(
238 &qh,
239 wl_surface::REQ_SET_BUFFER_SCALE_SINCE
240 ..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE,
241 (),
242 )
243 .unwrap(),
244 cursor_shape_manager: globals.bind(&qh, 1..=1, ()).ok(),
245 data_device_manager: globals
246 .bind(
247 &qh,
248 WL_DATA_DEVICE_MANAGER_VERSION..=WL_DATA_DEVICE_MANAGER_VERSION,
249 (),
250 )
251 .ok(),
252 primary_selection_manager: globals.bind(&qh, 1..=1, ()).ok(),
253 shm: globals.bind(&qh, 1..=1, ()).unwrap(),
254 seat,
255 wm_base: globals.bind(&qh, 1..=5, ()).unwrap(),
256 viewporter: globals.bind(&qh, 1..=1, ()).ok(),
257 fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
258 decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
259 layer_shell: globals.bind(&qh, 1..=5, ()).ok(),
260 blur_manager: globals.bind(&qh, 1..=1, ()).ok(),
261 text_input_manager: globals.bind(&qh, 1..=1, ()).ok(),
262 gesture_manager: globals.bind(&qh, 1..=3, ()).ok(),
263 dialog: globals.bind(&qh, dialog_v..=dialog_v, ()).ok(),
264 system_bell: globals.bind(&qh, 1..=1, ()).ok(),
265 executor,
266 qh,
267 }
268 }
269}
270
271#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
272pub struct InProgressOutput {
273 name: Option<String>,
274 scale: Option<i32>,
275 position: Option<Point<DevicePixels>>,
276 size: Option<Size<DevicePixels>>,
277 subpixel: Option<wl_output::Subpixel>,
278}
279
280impl InProgressOutput {
281 fn complete(&self) -> Option<Output> {
282 if let Some((position, size)) = self.position.zip(self.size) {
283 let scale = self.scale.unwrap_or(1);
284 Some(Output {
285 name: self.name.clone(),
286 scale,
287 bounds: Bounds::new(position, size),
288 subpixel: self.subpixel,
289 })
290 } else {
291 None
292 }
293 }
294}
295
296#[derive(Debug, Clone, Eq, PartialEq, Hash)]
297pub struct Output {
298 pub name: Option<String>,
299 pub scale: i32,
300 pub bounds: Bounds<DevicePixels>,
301 pub subpixel: Option<wl_output::Subpixel>,
302}
303
304pub(crate) struct WaylandClientState {
305 serial_tracker: SerialTracker,
306 globals: Globals,
307 pub gpu_context: GpuContext,
308 pub compositor_gpu: Option<CompositorGpuHint>,
309 wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
310 wl_pointer: Option<wl_pointer::WlPointer>,
311 pinch_gesture: Option<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1>,
312 pinch_scale: f32,
313 wl_keyboard: Option<wl_keyboard::WlKeyboard>,
314 cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
315 data_device: Option<wl_data_device::WlDataDevice>,
316 primary_selection: Option<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1>,
317 text_input: Option<zwp_text_input_v3::ZwpTextInputV3>,
318 pre_edit_text: Option<String>,
319 ime_pre_edit: Option<String>,
320 composing: bool,
321 last_ime_cursor_rectangle: Option<ImeCursorRectangle>,
322 // Surface to Window mapping
323 windows: HashMap<ObjectId, WaylandWindowStatePtr>,
324 // Output to scale mapping
325 outputs: HashMap<ObjectId, Output>,
326 in_progress_outputs: HashMap<ObjectId, InProgressOutput>,
327 wl_outputs: HashMap<ObjectId, wl_output::WlOutput>,
328 keyboard_layout: LinuxKeyboardLayout,
329 keymap_state: Option<xkb::State>,
330 compose_state: Option<xkb::compose::State>,
331 drag: DragState,
332 click: ClickState,
333 repeat: KeyRepeat,
334 pub modifiers: Modifiers,
335 pub capslock: Capslock,
336 axis_source: AxisSource,
337 pub mouse_location: Option<Point<Pixels>>,
338 continuous_scroll_delta: Option<Point<Pixels>>,
339 discrete_scroll_delta: Option<Point<f32>>,
340 vertical_modifier: f32,
341 horizontal_modifier: f32,
342 scroll_event_received: bool,
343 enter_token: Option<()>,
344 button_pressed: Option<MouseButton>,
345 mouse_focused_window: Option<WaylandWindowStatePtr>,
346 keyboard_focused_window: Option<WaylandWindowStatePtr>,
347 loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
348 cursor_style: Option<CursorStyle>,
349 cursor_hidden_window: Option<WaylandWindowStatePtr>,
350 clipboard: Clipboard,
351 data_offers: Vec<DataOffer<WlDataOffer>>,
352 primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
353 cursor: Cursor,
354 pending_activation: Option<PendingActivation>,
355 startup_activation_token: Option<String>,
356 event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
357 pub common: LinuxCommon,
358 ime_enabled: Option<bool>,
359}
360
361pub struct DragState {
362 data_offer: Option<wl_data_offer::WlDataOffer>,
363 window: Option<WaylandWindowStatePtr>,
364 position: Point<Pixels>,
365}
366
367pub struct ClickState {
368 last_mouse_button: Option<MouseButton>,
369 last_click: Instant,
370 last_location: Point<Pixels>,
371 current_count: usize,
372}
373
374pub(crate) struct KeyRepeat {
375 characters_per_second: u32,
376 delay: Duration,
377 current_id: u64,
378 current_keycode: Option<xkb::Keycode>,
379}
380
381pub(crate) enum PendingActivation {
382 /// URI to open in the web browser.
383 Uri(String),
384 /// Path to open in the file explorer.
385 Path(PathBuf),
386 /// A window from ourselves to raise.
387 Window(ObjectId),
388}
389
390impl WaylandClientState {
391 fn consume_startup_activation_token(&mut self, surface: &wl_surface::WlSurface) {
392 let Some(startup_activation_token) = self.startup_activation_token.take() else {
393 return;
394 };
395 let Some(activation) = self.globals.activation.as_ref() else {
396 return;
397 };
398 activation.activate(startup_activation_token, surface);
399 }
400}
401
402/// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the
403/// window to GPUI.
404#[derive(Clone)]
405pub struct WaylandClientStatePtr(Weak<RefCell<WaylandClientState>>);
406
407impl WaylandClientStatePtr {
408 pub fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {
409 self.0
410 .upgrade()
411 .expect("The pointer should always be valid when dispatching in wayland")
412 }
413
414 pub fn get_serial(&self, kind: SerialKind) -> Serial {
415 self.0.upgrade().unwrap().borrow().serial_tracker.get(kind)
416 }
417
418 pub fn set_pending_activation(&self, window: ObjectId) {
419 self.0.upgrade().unwrap().borrow_mut().pending_activation =
420 Some(PendingActivation::Window(window));
421 }
422
423 pub fn enable_ime(&self) {
424 let client = self.get_client();
425 let mut state = client.borrow_mut();
426 state.ime_enabled = Some(true);
427 state.last_ime_cursor_rectangle = None;
428 let Some(text_input) = state.text_input.take() else {
429 return;
430 };
431
432 text_input.enable();
433 text_input.set_content_type(ContentHint::None, ContentPurpose::Normal);
434 let mut cursor_rectangle = None;
435 if let Some(window) = state.keyboard_focused_window.clone() {
436 drop(state);
437 if let Some(area) = window.get_ime_area() {
438 let area = ImeCursorRectangle::from(area);
439 set_ime_cursor_rectangle(&text_input, area);
440 cursor_rectangle = Some(area);
441 }
442 state = client.borrow_mut();
443 }
444 text_input.commit();
445 state.last_ime_cursor_rectangle = cursor_rectangle;
446 state.text_input = Some(text_input);
447 }
448
449 pub fn disable_ime(&self) {
450 let client = self.get_client();
451 let mut state = client.borrow_mut();
452 state.ime_enabled = Some(false);
453 state.composing = false;
454 if let Some(text_input) = &state.text_input {
455 text_input.disable();
456 text_input.commit();
457 }
458 }
459
460 pub fn ime_enabled(&self) -> Option<bool> {
461 let client = self.get_client();
462 client.borrow().ime_enabled
463 }
464
465 pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
466 let client = self.get_client();
467 let mut state = client.borrow_mut();
468 if state.pre_edit_text.is_some() {
469 return;
470 }
471 let Some(text_input) = state.text_input.clone() else {
472 return;
473 };
474 update_ime_cursor_rectangle(&text_input, &mut state.last_ime_cursor_rectangle, bounds);
475 }
476
477 pub fn handle_keyboard_layout_change(&self) {
478 let client = self.get_client();
479 let mut state = client.borrow_mut();
480 let changed = if let Some(keymap_state) = &state.keymap_state {
481 let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
482 let keymap = keymap_state.get_keymap();
483 let layout_name = keymap.layout_get_name(layout_idx);
484 let changed = layout_name != state.keyboard_layout.name();
485 if changed {
486 state.keyboard_layout = LinuxKeyboardLayout::new(layout_name.to_string().into());
487 }
488 changed
489 } else {
490 let changed = &UNKNOWN_KEYBOARD_LAYOUT_NAME != state.keyboard_layout.name();
491 if changed {
492 state.keyboard_layout = LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME);
493 }
494 changed
495 };
496
497 if changed && let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take()
498 {
499 drop(state);
500 callback();
501 state = client.borrow_mut();
502 state.common.callbacks.keyboard_layout_change = Some(callback);
503 }
504 }
505
506 pub fn drop_window(&self, surface_id: &ObjectId) {
507 let client = self.get_client();
508 let mut state = client.borrow_mut();
509 let closed_window = state.windows.remove(surface_id).unwrap();
510 if let Some(window) = state.mouse_focused_window.take()
511 && !window.ptr_eq(&closed_window)
512 {
513 state.mouse_focused_window = Some(window);
514 }
515 if let Some(window) = state.keyboard_focused_window.take()
516 && !window.ptr_eq(&closed_window)
517 {
518 state.keyboard_focused_window = Some(window);
519 }
520 if let Some(window) = state.cursor_hidden_window.take()
521 && !window.ptr_eq(&closed_window)
522 {
523 state.cursor_hidden_window = Some(window);
524 }
525 }
526}
527
528impl WaylandClientState {
529 fn hide_cursor_until_mouse_moves(&mut self) {
530 if self.cursor_hidden_window.is_some() {
531 return;
532 }
533 let Some(focused_window) = self.mouse_focused_window.clone() else {
534 // No surface to apply the hidden cursor to.
535 return;
536 };
537 let Some(wl_pointer) = self.wl_pointer.clone() else {
538 // Seat lost its pointer capability; nothing to hide.
539 return;
540 };
541 let serial = self.serial_tracker.get(SerialKind::MouseEnter);
542 wl_pointer.set_cursor(serial.as_raw(), None, 0, 0);
543 self.cursor_hidden_window = Some(focused_window);
544 }
545
546 fn restore_cursor_after_hide(&mut self) {
547 if self.cursor_hidden_window.take().is_none() {
548 return;
549 }
550 let Some(style) = self.cursor_style else {
551 return;
552 };
553 let serial = self.serial_tracker.get(SerialKind::MouseEnter);
554 if let Some(cursor_shape_device) = &self.cursor_shape_device {
555 cursor_shape_device.set_shape(serial.as_raw(), to_shape(style));
556 return;
557 }
558 let Some(focused_window) = self.mouse_focused_window.clone() else {
559 log::warn!(
560 "wayland: no focused surface to restore cursor style {:?} after hide; cursor may stay invisible",
561 style
562 );
563 return;
564 };
565 let Some(wl_pointer) = self.wl_pointer.clone() else {
566 log::warn!(
567 "wayland: no wl_pointer to restore cursor style {:?} after hide; cursor may stay invisible",
568 style
569 );
570 return;
571 };
572 let scale = focused_window.primary_output_scale();
573 self.cursor.set_icon(
574 &wl_pointer,
575 serial.as_raw(),
576 cursor_style_to_icon_names(style),
577 scale,
578 );
579 }
580}
581
582#[derive(Clone)]
583pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
584
585impl Drop for WaylandClient {
586 fn drop(&mut self) {
587 let mut state = self.0.borrow_mut();
588 state.windows.clear();
589
590 if let Some(wl_pointer) = &state.wl_pointer {
591 wl_pointer.release();
592 }
593 if let Some(cursor_shape_device) = &state.cursor_shape_device {
594 cursor_shape_device.destroy();
595 }
596 if let Some(data_device) = &state.data_device {
597 data_device.release();
598 }
599 if let Some(text_input) = &state.text_input {
600 text_input.destroy();
601 }
602 }
603}
604
605const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3;
606
607fn wl_seat_version(version: u32) -> u32 {
608 // We rely on the wl_pointer.frame event
609 const WL_SEAT_MIN_VERSION: u32 = 5;
610 const WL_SEAT_MAX_VERSION: u32 = 9;
611
612 if version < WL_SEAT_MIN_VERSION {
613 panic!(
614 "wl_seat below required version: {} < {}",
615 version, WL_SEAT_MIN_VERSION
616 );
617 }
618
619 version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)
620}
621
622fn wl_output_version(version: u32) -> u32 {
623 const WL_OUTPUT_MIN_VERSION: u32 = 2;
624 const WL_OUTPUT_MAX_VERSION: u32 = 4;
625
626 if version < WL_OUTPUT_MIN_VERSION {
627 panic!(
628 "wl_output below required version: {} < {}",
629 version, WL_OUTPUT_MIN_VERSION
630 );
631 }
632
633 version.clamp(WL_OUTPUT_MIN_VERSION, WL_OUTPUT_MAX_VERSION)
634}
635
636impl WaylandClient {
637 pub(crate) fn new() -> Self {
638 let startup_activation_token = take_startup_activation_token_from_environment();
639 let conn = Connection::connect_to_env().unwrap();
640
641 let (globals, event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
642 let qh = event_queue.handle();
643
644 let mut seat: Option<wl_seat::WlSeat> = None;
645 #[allow(clippy::mutable_key_type)]
646 let mut in_progress_outputs = HashMap::default();
647 #[allow(clippy::mutable_key_type)]
648 let mut wl_outputs: HashMap<ObjectId, wl_output::WlOutput> = HashMap::default();
649 globals.contents().with_list(|list| {
650 for global in list {
651 match &global.interface[..] {
652 "wl_seat" => {
653 seat = Some(globals.registry().bind::<wl_seat::WlSeat, _, _>(
654 global.name,
655 wl_seat_version(global.version),
656 &qh,
657 (),
658 ));
659 }
660 "wl_output" => {
661 let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
662 global.name,
663 wl_output_version(global.version),
664 &qh,
665 (),
666 );
667 in_progress_outputs.insert(output.id(), InProgressOutput::default());
668 wl_outputs.insert(output.id(), output);
669 }
670 _ => {}
671 }
672 }
673 });
674
675 let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
676
677 let (common, main_receiver, wake_receiver) = LinuxCommon::new(event_loop.get_signal());
678
679 let handle = event_loop.handle();
680 handle
681 .insert_source(main_receiver, {
682 let handle = handle.clone();
683 move |event, _, _: &mut WaylandClientStatePtr| {
684 if let calloop::channel::Event::Msg(runnable) = event {
685 handle.insert_idle(|_| {
686 let location = runnable.metadata().location;
687 let spawned = runnable.metadata().spawned;
688 profiler::update_running_task(spawned, location);
689 runnable.run();
690 profiler::save_task_timing();
691 });
692 }
693 }
694 })
695 .unwrap();
696
697 handle
698 .insert_source(
699 wake_receiver,
700 |event, _, client: &mut WaylandClientStatePtr| {
701 if let calloop::channel::Event::Msg(()) = event {
702 client.get_client().borrow_mut().common.handle_system_wake();
703 }
704 },
705 )
706 .unwrap();
707
708 let compositor_gpu = detect_compositor_gpu();
709 let gpu_context = Rc::new(RefCell::new(None));
710
711 let seat = seat.unwrap();
712 let globals = Globals::new(
713 globals,
714 common.foreground_executor.clone(),
715 qh.clone(),
716 seat.clone(),
717 );
718
719 let data_device = globals
720 .data_device_manager
721 .as_ref()
722 .map(|data_device_manager| data_device_manager.get_data_device(&seat, &qh, ()));
723
724 let primary_selection = globals
725 .primary_selection_manager
726 .as_ref()
727 .map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
728
729 let cursor = Cursor::new(&conn, &globals, 24);
730
731 handle
732 .insert_source(XDPEventSource::new(&common.background_executor), {
733 move |event, _, client| match event {
734 XDPEvent::WindowAppearance(appearance) => {
735 if let Some(client) = client.0.upgrade() {
736 let mut client = client.borrow_mut();
737
738 client.common.appearance = appearance;
739
740 for window in client.windows.values_mut() {
741 window.set_appearance(appearance);
742 }
743 }
744 }
745 XDPEvent::ButtonLayout(layout_str) => {
746 if let Some(client) = client.0.upgrade() {
747 let layout = WindowButtonLayout::parse(&layout_str)
748 .log_err()
749 .unwrap_or_else(WindowButtonLayout::linux_default);
750 let mut client = client.borrow_mut();
751 client.common.button_layout = layout;
752
753 for window in client.windows.values_mut() {
754 window.set_button_layout();
755 }
756 }
757 }
758 XDPEvent::CursorTheme(theme) => {
759 if let Some(client) = client.0.upgrade() {
760 let mut client = client.borrow_mut();
761 client.cursor.set_theme(theme);
762 }
763 }
764 XDPEvent::CursorSize(size) => {
765 if let Some(client) = client.0.upgrade() {
766 let mut client = client.borrow_mut();
767 client.cursor.set_size(size);
768 }
769 }
770 }
771 })
772 .unwrap();
773
774 let state = Rc::new(RefCell::new(WaylandClientState {
775 serial_tracker: SerialTracker::new(),
776 globals,
777 gpu_context,
778 compositor_gpu,
779 wl_seat: seat,
780 wl_pointer: None,
781 wl_keyboard: None,
782 pinch_gesture: None,
783 pinch_scale: 1.0,
784 cursor_shape_device: None,
785 data_device,
786 primary_selection,
787 text_input: None,
788 pre_edit_text: None,
789 ime_pre_edit: None,
790 composing: false,
791 last_ime_cursor_rectangle: None,
792 outputs: HashMap::default(),
793 in_progress_outputs,
794 wl_outputs,
795 windows: HashMap::default(),
796 common,
797 keyboard_layout: LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME),
798 keymap_state: None,
799 compose_state: None,
800 drag: DragState {
801 data_offer: None,
802 window: None,
803 position: Point::default(),
804 },
805 click: ClickState {
806 last_click: Instant::now(),
807 last_mouse_button: None,
808 last_location: Point::default(),
809 current_count: 0,
810 },
811 repeat: KeyRepeat {
812 characters_per_second: 16,
813 delay: Duration::from_millis(500),
814 current_id: 0,
815 current_keycode: None,
816 },
817 modifiers: Modifiers {
818 shift: false,
819 control: false,
820 alt: false,
821 function: false,
822 platform: false,
823 },
824 capslock: Capslock { on: false },
825 scroll_event_received: false,
826 axis_source: AxisSource::Wheel,
827 mouse_location: None,
828 continuous_scroll_delta: None,
829 discrete_scroll_delta: None,
830 vertical_modifier: -1.0,
831 horizontal_modifier: -1.0,
832 button_pressed: None,
833 mouse_focused_window: None,
834 keyboard_focused_window: None,
835 loop_handle: handle.clone(),
836 enter_token: None,
837 cursor_style: None,
838 cursor_hidden_window: None,
839 clipboard: Clipboard::new(conn.clone(), handle.clone()),
840 data_offers: Vec::new(),
841 primary_data_offer: None,
842 cursor,
843 pending_activation: None,
844 startup_activation_token,
845 event_loop: Some(event_loop),
846 ime_enabled: None,
847 }));
848
849 WaylandSource::new(conn, event_queue)
850 .insert(handle)
851 .unwrap();
852
853 Self(state)
854 }
855}
856
857impl LinuxClient for WaylandClient {
858 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
859 Box::new(self.0.borrow().keyboard_layout.clone())
860 }
861
862 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
863 self.0
864 .borrow()
865 .outputs
866 .iter()
867 .map(|(id, output)| {
868 Rc::new(WaylandDisplay {
869 id: id.clone(),
870 name: output.name.clone(),
871 bounds: output.bounds.to_pixels(output.scale as f32),
872 }) as Rc<dyn PlatformDisplay>
873 })
874 .collect()
875 }
876
877 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
878 self.0
879 .borrow()
880 .outputs
881 .iter()
882 .find_map(|(object_id, output)| {
883 (object_id.protocol_id() as u64 == u64::from(id)).then(|| {
884 Rc::new(WaylandDisplay {
885 id: object_id.clone(),
886 name: output.name.clone(),
887 bounds: output.bounds.to_pixels(output.scale as f32),
888 }) as Rc<dyn PlatformDisplay>
889 })
890 })
891 }
892
893 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
894 None
895 }
896
897 #[cfg(feature = "screen-capture")]
898 fn screen_capture_sources(
899 &self,
900 ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
901 {
902 // TODO: Get screen capture working on wayland. Be sure to try window resizing as that may
903 // be tricky.
904 //
905 // start_scap_default_target_source()
906 let (sources_tx, sources_rx) = futures::channel::oneshot::channel();
907 sources_tx
908 .send(Err(anyhow::anyhow!(
909 "Wayland screen capture not yet implemented."
910 )))
911 .ok();
912 sources_rx
913 }
914
915 fn open_window(
916 &self,
917 handle: AnyWindowHandle,
918 params: WindowParams,
919 ) -> anyhow::Result<Box<dyn PlatformWindow>> {
920 let mut state = self.0.borrow_mut();
921
922 // Popups name their parent explicitly. Other kinds are parented to the focused window.
923 let (parent, popup_grab) = match ¶ms.kind {
924 WindowKind::AnchoredPopup(options) => {
925 let parent = state
926 .windows
927 .values()
928 .find(|window| window.handle() == options.parent)
929 .cloned()
930 .ok_or_else(|| anyhow::anyhow!("popup parent window not found"))?;
931 // A popup grab must reference a press event or the compositor declines it and
932 // immediately dismisses the popup, so use the most recent press serial, or no
933 // grab before any press.
934 let popup_grab = options.grab.then(|| {
935 let serial = state
936 .serial_tracker
937 .get(SerialKind::MousePress)
938 .as_raw()
939 .max(state.serial_tracker.get(SerialKind::KeyPress).as_raw());
940 (serial != 0).then(|| (serial, state.wl_seat.clone()))
941 });
942 (Some(parent), popup_grab.flatten())
943 }
944 _ => (state.keyboard_focused_window.clone(), None),
945 };
946
947 let target_output = params.display_id.and_then(|display_id| {
948 let target_protocol_id: u64 = display_id.into();
949 state
950 .wl_outputs
951 .iter()
952 .find(|(id, _)| id.protocol_id() as u64 == target_protocol_id)
953 .map(|(_, output)| output.clone())
954 });
955
956 let appearance = state.common.appearance;
957 let compositor_gpu = state.compositor_gpu.take();
958
959 let (window, surface_id) = WaylandWindow::new(
960 handle,
961 state.globals.clone(),
962 state.gpu_context.clone(),
963 compositor_gpu,
964 WaylandClientStatePtr(Rc::downgrade(&self.0)),
965 params,
966 appearance,
967 parent,
968 popup_grab,
969 target_output,
970 )?;
971
972 if window.0.toplevel().is_some() {
973 state.consume_startup_activation_token(&window.0.surface());
974 }
975 state.windows.insert(surface_id, window.0.clone());
976
977 Ok(Box::new(window))
978 }
979
980 fn set_cursor_style(&self, style: CursorStyle) {
981 let mut state = self.0.borrow_mut();
982
983 let need_update = state.cursor_style != Some(style)
984 && (state.mouse_focused_window.is_none()
985 || state
986 .mouse_focused_window
987 .as_ref()
988 .is_some_and(|w| !w.is_blocked()));
989
990 if !need_update {
991 return;
992 }
993
994 state.cursor_style = Some(style);
995
996 // Don't clobber the invisible cursor; restore reads back from `cursor_style`.
997 if state.cursor_hidden_window.is_some() {
998 return;
999 }
1000
1001 let serial = state.serial_tracker.get(SerialKind::MouseEnter);
1002 if let Some(cursor_shape_device) = &state.cursor_shape_device {
1003 cursor_shape_device.set_shape(serial.as_raw(), to_shape(style));
1004 } else if let Some(focused_window) = &state.mouse_focused_window {
1005 // cursor-shape-v1 isn't supported, set the cursor using a surface.
1006 let wl_pointer = state
1007 .wl_pointer
1008 .clone()
1009 .expect("window is focused by pointer");
1010 let scale = focused_window.primary_output_scale();
1011 state.cursor.set_icon(
1012 &wl_pointer,
1013 serial.as_raw(),
1014 cursor_style_to_icon_names(style),
1015 scale,
1016 );
1017 }
1018 }
1019
1020 fn hide_cursor_until_mouse_moves(&self) {
1021 self.0.borrow_mut().hide_cursor_until_mouse_moves();
1022 }
1023
1024 fn is_cursor_visible(&self) -> bool {
1025 self.0.borrow().cursor_hidden_window.is_none()
1026 }
1027
1028 fn open_uri(&self, uri: &str) {
1029 let mut state = self.0.borrow_mut();
1030 if let (Some(activation), Some(window)) = (
1031 state.globals.activation.clone(),
1032 state.mouse_focused_window.clone(),
1033 ) {
1034 state.pending_activation = Some(PendingActivation::Uri(uri.to_string()));
1035 let token = activation.get_activation_token(&state.globals.qh, ());
1036 let serial = state.serial_tracker.get(SerialKind::MousePress);
1037 token.set_serial(serial.as_raw(), &state.wl_seat);
1038 token.set_surface(&window.surface());
1039 token.commit();
1040 } else {
1041 let executor = state.common.background_executor.clone();
1042 open_uri_internal(executor, uri, None);
1043 }
1044 }
1045
1046 fn reveal_path(&self, path: PathBuf) {
1047 let mut state = self.0.borrow_mut();
1048 if let (Some(activation), Some(window)) = (
1049 state.globals.activation.clone(),
1050 state.mouse_focused_window.clone(),
1051 ) {
1052 state.pending_activation = Some(PendingActivation::Path(path));
1053 let token = activation.get_activation_token(&state.globals.qh, ());
1054 let serial = state.serial_tracker.get(SerialKind::MousePress);
1055 token.set_serial(serial.as_raw(), &state.wl_seat);
1056 token.set_surface(&window.surface());
1057 token.commit();
1058 } else {
1059 let executor = state.common.background_executor.clone();
1060 reveal_path_internal(executor, path, None);
1061 }
1062 }
1063
1064 fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
1065 f(&mut self.0.borrow_mut().common)
1066 }
1067
1068 fn run(&self) {
1069 let mut event_loop = self
1070 .0
1071 .borrow_mut()
1072 .event_loop
1073 .take()
1074 .expect("App is already running");
1075
1076 event_loop
1077 .run(
1078 None,
1079 &mut WaylandClientStatePtr(Rc::downgrade(&self.0)),
1080 |_| {},
1081 )
1082 .log_err();
1083 }
1084
1085 fn write_to_primary(&self, item: gpui::ClipboardItem) {
1086 let mut state = self.0.borrow_mut();
1087 let (Some(primary_selection_manager), Some(primary_selection)) = (
1088 state.globals.primary_selection_manager.clone(),
1089 state.primary_selection.clone(),
1090 ) else {
1091 return;
1092 };
1093 if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
1094 state.clipboard.set_primary(item);
1095 let Some(serial) = state.serial_tracker.selection_serial() else {
1096 log::warn!(
1097 "Skipping Wayland primary selection ownership request because no keyboard or pointer press serial has been received"
1098 );
1099 return;
1100 };
1101 let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
1102 for mime_type in TEXT_MIME_TYPES {
1103 data_source.offer(mime_type.to_string());
1104 }
1105 data_source.offer(state.clipboard.self_mime());
1106 primary_selection.set_selection(Some(&data_source), serial.as_raw());
1107 }
1108 }
1109
1110 fn write_to_clipboard(&self, item: gpui::ClipboardItem) {
1111 let mut state = self.0.borrow_mut();
1112 let (Some(data_device_manager), Some(data_device)) = (
1113 state.globals.data_device_manager.clone(),
1114 state.data_device.clone(),
1115 ) else {
1116 return;
1117 };
1118 if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
1119 state.clipboard.set(item);
1120 let Some(serial) = state.serial_tracker.selection_serial() else {
1121 log::warn!(
1122 "Skipping Wayland clipboard ownership request because no keyboard or pointer press serial has been received"
1123 );
1124 return;
1125 };
1126 let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
1127 for mime_type in TEXT_MIME_TYPES {
1128 data_source.offer(mime_type.to_string());
1129 }
1130 data_source.offer(state.clipboard.self_mime());
1131 data_device.set_selection(Some(&data_source), serial.as_raw());
1132 }
1133 }
1134
1135 fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
1136 self.0.borrow_mut().clipboard.read_primary()
1137 }
1138
1139 fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
1140 self.0.borrow_mut().clipboard.read()
1141 }
1142
1143 fn active_window(&self) -> Option<AnyWindowHandle> {
1144 self.0
1145 .borrow_mut()
1146 .keyboard_focused_window
1147 .as_ref()
1148 .map(|window| window.handle())
1149 }
1150
1151 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
1152 None
1153 }
1154
1155 fn compositor_name(&self) -> &'static str {
1156 "Wayland"
1157 }
1158
1159 fn window_identifier(&self) -> impl Future<Output = Option<WindowIdentifier>> + Send + 'static {
1160 async fn inner(surface: Option<wl_surface::WlSurface>) -> Option<WindowIdentifier> {
1161 if let Some(surface) = surface {
1162 ashpd::WindowIdentifier::from_wayland(&surface).await
1163 } else {
1164 None
1165 }
1166 }
1167
1168 let client_state = self.0.borrow();
1169 let active_window = client_state.keyboard_focused_window.as_ref();
1170 inner(active_window.map(|aw| aw.surface()))
1171 }
1172}
1173
1174struct DmabufProbeState {
1175 device: Option<u64>,
1176}
1177
1178impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for DmabufProbeState {
1179 fn event(
1180 _: &mut Self,
1181 _: &wl_registry::WlRegistry,
1182 _: wl_registry::Event,
1183 _: &GlobalListContents,
1184 _: &Connection,
1185 _: &QueueHandle<Self>,
1186 ) {
1187 }
1188}
1189
1190impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for DmabufProbeState {
1191 fn event(
1192 _: &mut Self,
1193 _: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
1194 _: zwp_linux_dmabuf_v1::Event,
1195 _: &(),
1196 _: &Connection,
1197 _: &QueueHandle<Self>,
1198 ) {
1199 }
1200}
1201
1202impl Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, ()> for DmabufProbeState {
1203 fn event(
1204 state: &mut Self,
1205 _: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
1206 event: zwp_linux_dmabuf_feedback_v1::Event,
1207 _: &(),
1208 _: &Connection,
1209 _: &QueueHandle<Self>,
1210 ) {
1211 if let zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } = event {
1212 if let Ok(bytes) = <[u8; 8]>::try_from(device.as_slice()) {
1213 state.device = Some(u64::from_ne_bytes(bytes));
1214 }
1215 }
1216 }
1217}
1218
1219fn detect_compositor_gpu() -> Option<CompositorGpuHint> {
1220 let connection = Connection::connect_to_env().ok()?;
1221 let (globals, mut event_queue) = registry_queue_init::<DmabufProbeState>(&connection).ok()?;
1222 let queue_handle = event_queue.handle();
1223
1224 let dmabuf: zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 =
1225 globals.bind(&queue_handle, 4..=4, ()).ok()?;
1226 let feedback = dmabuf.get_default_feedback(&queue_handle, ());
1227
1228 let mut state = DmabufProbeState { device: None };
1229
1230 event_queue.roundtrip(&mut state).ok()?;
1231
1232 feedback.destroy();
1233 dmabuf.destroy();
1234
1235 crate::linux::compositor_gpu_hint_from_dev_t(state.device?)
1236}
1237
1238impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
1239 fn event(
1240 this: &mut Self,
1241 registry: &wl_registry::WlRegistry,
1242 event: wl_registry::Event,
1243 _: &GlobalListContents,
1244 _: &Connection,
1245 qh: &QueueHandle<Self>,
1246 ) {
1247 let client = this.get_client();
1248 let mut state = client.borrow_mut();
1249
1250 match event {
1251 wl_registry::Event::Global {
1252 name,
1253 interface,
1254 version,
1255 } => match &interface[..] {
1256 "wl_seat" => {
1257 if let Some(wl_pointer) = state.wl_pointer.take() {
1258 wl_pointer.release();
1259 }
1260 if let Some(wl_keyboard) = state.wl_keyboard.take() {
1261 wl_keyboard.release();
1262 }
1263 state.wl_seat.release();
1264 state.wl_seat = registry.bind::<wl_seat::WlSeat, _, _>(
1265 name,
1266 wl_seat_version(version),
1267 qh,
1268 (),
1269 );
1270 }
1271 "wl_output" => {
1272 let output = registry.bind::<wl_output::WlOutput, _, _>(
1273 name,
1274 wl_output_version(version),
1275 qh,
1276 (),
1277 );
1278
1279 state
1280 .in_progress_outputs
1281 .insert(output.id(), InProgressOutput::default());
1282 state.wl_outputs.insert(output.id(), output);
1283 }
1284 _ => {}
1285 },
1286 wl_registry::Event::GlobalRemove { name: _ } => {
1287 // TODO: handle global removal
1288 }
1289 _ => {}
1290 }
1291 }
1292}
1293
1294delegate_noop!(WaylandClientStatePtr: ignore xdg_activation_v1::XdgActivationV1);
1295delegate_noop!(WaylandClientStatePtr: ignore xdg_system_bell_v1::XdgSystemBellV1);
1296delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor);
1297delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_device_v1::WpCursorShapeDeviceV1);
1298delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_manager_v1::WpCursorShapeManagerV1);
1299delegate_noop!(WaylandClientStatePtr: ignore wl_data_device_manager::WlDataDeviceManager);
1300delegate_noop!(WaylandClientStatePtr: ignore zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1);
1301delegate_noop!(WaylandClientStatePtr: ignore wl_shm::WlShm);
1302delegate_noop!(WaylandClientStatePtr: ignore wl_shm_pool::WlShmPool);
1303delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
1304delegate_noop!(WaylandClientStatePtr: ignore wl_region::WlRegion);
1305delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
1306delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
1307delegate_noop!(WaylandClientStatePtr: ignore zwlr_layer_shell_v1::ZwlrLayerShellV1);
1308delegate_noop!(WaylandClientStatePtr: ignore xdg_positioner::XdgPositioner);
1309delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur_manager::OrgKdeKwinBlurManager);
1310delegate_noop!(WaylandClientStatePtr: ignore zwp_text_input_manager_v3::ZwpTextInputManagerV3);
1311delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur::OrgKdeKwinBlur);
1312delegate_noop!(WaylandClientStatePtr: ignore wp_viewporter::WpViewporter);
1313delegate_noop!(WaylandClientStatePtr: ignore wp_viewport::WpViewport);
1314
1315impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
1316 fn event(
1317 state: &mut WaylandClientStatePtr,
1318 _: &wl_callback::WlCallback,
1319 event: wl_callback::Event,
1320 surface_id: &ObjectId,
1321 _: &Connection,
1322 _: &QueueHandle<Self>,
1323 ) {
1324 let client = state.get_client();
1325 let mut state = client.borrow_mut();
1326 let Some(window) = get_window(&mut state, surface_id) else {
1327 return;
1328 };
1329 drop(state);
1330
1331 if let wl_callback::Event::Done { .. } = event {
1332 window.frame();
1333 }
1334 }
1335}
1336
1337pub(crate) fn get_window(
1338 state: &mut RefMut<WaylandClientState>,
1339 surface_id: &ObjectId,
1340) -> Option<WaylandWindowStatePtr> {
1341 state.windows.get(surface_id).cloned()
1342}
1343
1344impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
1345 fn event(
1346 this: &mut Self,
1347 surface: &wl_surface::WlSurface,
1348 event: <wl_surface::WlSurface as Proxy>::Event,
1349 _: &(),
1350 _: &Connection,
1351 _: &QueueHandle<Self>,
1352 ) {
1353 let client = this.get_client();
1354 let mut state = client.borrow_mut();
1355
1356 let Some(window) = get_window(&mut state, &surface.id()) else {
1357 return;
1358 };
1359 #[allow(clippy::mutable_key_type)]
1360 let outputs = state.outputs.clone();
1361 drop(state);
1362
1363 window.handle_surface_event(event, outputs);
1364 }
1365}
1366
1367impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
1368 fn event(
1369 this: &mut Self,
1370 output: &wl_output::WlOutput,
1371 event: <wl_output::WlOutput as Proxy>::Event,
1372 _: &(),
1373 _: &Connection,
1374 _: &QueueHandle<Self>,
1375 ) {
1376 let client = this.get_client();
1377 let mut state = client.borrow_mut();
1378
1379 let Some(in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else {
1380 return;
1381 };
1382
1383 match event {
1384 wl_output::Event::Name { name } => {
1385 in_progress_output.name = Some(name);
1386 }
1387 wl_output::Event::Scale { factor } => {
1388 in_progress_output.scale = Some(factor);
1389 }
1390 wl_output::Event::Geometry { x, y, subpixel, .. } => {
1391 in_progress_output.position = Some(point(DevicePixels(x), DevicePixels(y)));
1392 if let WEnum::Value(subpixel) = subpixel {
1393 in_progress_output.subpixel = Some(subpixel);
1394 }
1395 }
1396 wl_output::Event::Mode { width, height, .. } => {
1397 in_progress_output.size = Some(size(DevicePixels(width), DevicePixels(height)))
1398 }
1399 wl_output::Event::Done => {
1400 if let Some(complete) = in_progress_output.complete() {
1401 state.outputs.insert(output.id(), complete);
1402 }
1403 state.in_progress_outputs.remove(&output.id());
1404 }
1405 _ => {}
1406 }
1407 }
1408}
1409
1410impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
1411 fn event(
1412 state: &mut Self,
1413 _: &xdg_surface::XdgSurface,
1414 event: xdg_surface::Event,
1415 surface_id: &ObjectId,
1416 _: &Connection,
1417 _: &QueueHandle<Self>,
1418 ) {
1419 let client = state.get_client();
1420 let mut state = client.borrow_mut();
1421 let Some(window) = get_window(&mut state, surface_id) else {
1422 return;
1423 };
1424 drop(state);
1425 window.handle_xdg_surface_event(event);
1426 }
1427}
1428
1429impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
1430 fn event(
1431 this: &mut Self,
1432 _: &xdg_toplevel::XdgToplevel,
1433 event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
1434 surface_id: &ObjectId,
1435 _: &Connection,
1436 _: &QueueHandle<Self>,
1437 ) {
1438 let client = this.get_client();
1439 let mut state = client.borrow_mut();
1440 let Some(window) = get_window(&mut state, surface_id) else {
1441 return;
1442 };
1443
1444 drop(state);
1445 let should_close = window.handle_toplevel_event(event);
1446
1447 if should_close {
1448 // The close logic will be handled in drop_window()
1449 window.close();
1450 }
1451 }
1452}
1453
1454impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ObjectId> for WaylandClientStatePtr {
1455 fn event(
1456 this: &mut Self,
1457 _: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
1458 event: <zwlr_layer_surface_v1::ZwlrLayerSurfaceV1 as Proxy>::Event,
1459 surface_id: &ObjectId,
1460 _: &Connection,
1461 _: &QueueHandle<Self>,
1462 ) {
1463 let client = this.get_client();
1464 let mut state = client.borrow_mut();
1465 let Some(window) = get_window(&mut state, surface_id) else {
1466 return;
1467 };
1468
1469 drop(state);
1470 let should_close = window.handle_layersurface_event(event);
1471
1472 if should_close {
1473 // Close logic will be handled in drop_window()
1474 window.close();
1475 }
1476 }
1477}
1478
1479impl Dispatch<xdg_popup::XdgPopup, ObjectId> for WaylandClientStatePtr {
1480 fn event(
1481 this: &mut Self,
1482 _: &xdg_popup::XdgPopup,
1483 event: <xdg_popup::XdgPopup as Proxy>::Event,
1484 surface_id: &ObjectId,
1485 _: &Connection,
1486 _: &QueueHandle<Self>,
1487 ) {
1488 let client = this.get_client();
1489 let mut state = client.borrow_mut();
1490 let Some(window) = get_window(&mut state, surface_id) else {
1491 return;
1492 };
1493
1494 drop(state);
1495 let should_close = window.handle_popup_event(event);
1496
1497 if should_close {
1498 // The close logic will be handled in drop_window()
1499 window.close();
1500 }
1501 }
1502}
1503
1504impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
1505 fn event(
1506 _: &mut Self,
1507 wm_base: &xdg_wm_base::XdgWmBase,
1508 event: <xdg_wm_base::XdgWmBase as Proxy>::Event,
1509 _: &(),
1510 _: &Connection,
1511 _: &QueueHandle<Self>,
1512 ) {
1513 if let xdg_wm_base::Event::Ping { serial } = event {
1514 wm_base.pong(serial);
1515 }
1516 }
1517}
1518
1519impl Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, ()> for WaylandClientStatePtr {
1520 fn event(
1521 this: &mut Self,
1522 token: &xdg_activation_token_v1::XdgActivationTokenV1,
1523 event: <xdg_activation_token_v1::XdgActivationTokenV1 as Proxy>::Event,
1524 _: &(),
1525 _: &Connection,
1526 _: &QueueHandle<Self>,
1527 ) {
1528 let client = this.get_client();
1529 let mut state = client.borrow_mut();
1530
1531 if let xdg_activation_token_v1::Event::Done { token } = event {
1532 let executor = state.common.background_executor.clone();
1533 match state.pending_activation.take() {
1534 Some(PendingActivation::Uri(uri)) => open_uri_internal(executor, &uri, Some(token)),
1535 Some(PendingActivation::Path(path)) => {
1536 reveal_path_internal(executor, path, Some(token))
1537 }
1538 Some(PendingActivation::Window(window)) => {
1539 let Some(window) = get_window(&mut state, &window) else {
1540 return;
1541 };
1542 let activation = state.globals.activation.as_ref().unwrap();
1543 activation.activate(token, &window.surface());
1544 }
1545 None => log::error!("activation token received with no pending activation"),
1546 }
1547 }
1548
1549 token.destroy();
1550 }
1551}
1552
1553impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
1554 fn event(
1555 state: &mut Self,
1556 seat: &wl_seat::WlSeat,
1557 event: wl_seat::Event,
1558 _: &(),
1559 _: &Connection,
1560 qh: &QueueHandle<Self>,
1561 ) {
1562 if let wl_seat::Event::Capabilities {
1563 capabilities: WEnum::Value(capabilities),
1564 } = event
1565 {
1566 let client = state.get_client();
1567 let mut state = client.borrow_mut();
1568 if capabilities.contains(wl_seat::Capability::Keyboard) {
1569 let keyboard = seat.get_keyboard(qh, ());
1570
1571 if let Some(text_input) = state.text_input.take() {
1572 text_input.destroy();
1573 state.ime_pre_edit = None;
1574 state.composing = false;
1575 }
1576
1577 state.text_input = state
1578 .globals
1579 .text_input_manager
1580 .as_ref()
1581 .map(|text_input_manager| text_input_manager.get_text_input(seat, qh, ()));
1582
1583 if let Some(wl_keyboard) = &state.wl_keyboard {
1584 wl_keyboard.release();
1585 }
1586
1587 state.wl_keyboard = Some(keyboard);
1588 }
1589 if capabilities.contains(wl_seat::Capability::Pointer) {
1590 let pointer = seat.get_pointer(qh, ());
1591
1592 if let Some(cursor_shape_device) = state.cursor_shape_device.take() {
1593 cursor_shape_device.destroy();
1594 }
1595
1596 state.cursor_shape_device = state
1597 .globals
1598 .cursor_shape_manager
1599 .as_ref()
1600 .map(|cursor_shape_manager| cursor_shape_manager.get_pointer(&pointer, qh, ()));
1601
1602 state.pinch_gesture = state.globals.gesture_manager.as_ref().map(
1603 |gesture_manager: &zwp_pointer_gestures_v1::ZwpPointerGesturesV1| {
1604 gesture_manager.get_pinch_gesture(&pointer, qh, ())
1605 },
1606 );
1607
1608 if let Some(wl_pointer) = &state.wl_pointer {
1609 wl_pointer.release();
1610 }
1611
1612 state.wl_pointer = Some(pointer);
1613 }
1614 }
1615 }
1616}
1617
1618impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
1619 fn event(
1620 this: &mut Self,
1621 _: &wl_keyboard::WlKeyboard,
1622 event: wl_keyboard::Event,
1623 _: &(),
1624 _: &Connection,
1625 _: &QueueHandle<Self>,
1626 ) {
1627 let client = this.get_client();
1628 let mut state = client.borrow_mut();
1629 match event {
1630 wl_keyboard::Event::RepeatInfo { rate, delay } => {
1631 state.repeat.characters_per_second = rate as u32;
1632 state.repeat.delay = Duration::from_millis(delay as u64);
1633 }
1634 wl_keyboard::Event::Keymap {
1635 format: WEnum::Value(format),
1636 fd,
1637 size,
1638 ..
1639 } => {
1640 if format != wl_keyboard::KeymapFormat::XkbV1 {
1641 log::error!("Received keymap format {:?}, expected XkbV1", format);
1642 return;
1643 }
1644 let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
1645 let keymap = unsafe {
1646 xkb::Keymap::new_from_fd(
1647 &xkb_context,
1648 fd,
1649 size as usize,
1650 XKB_KEYMAP_FORMAT_TEXT_V1,
1651 KEYMAP_COMPILE_NO_FLAGS,
1652 )
1653 .log_err()
1654 .flatten()
1655 .expect("Failed to create keymap")
1656 };
1657 state.keymap_state = Some(xkb::State::new(&keymap));
1658 state.compose_state = get_xkb_compose_state(&xkb_context);
1659 drop(state);
1660
1661 this.handle_keyboard_layout_change();
1662 }
1663 wl_keyboard::Event::Enter { surface, .. } => {
1664 state.keyboard_focused_window = get_window(&mut state, &surface.id());
1665 state.enter_token = Some(());
1666
1667 if let Some(window) = state.keyboard_focused_window.clone() {
1668 drop(state);
1669 window.set_focused(true);
1670 }
1671 }
1672 wl_keyboard::Event::Leave { surface, .. } => {
1673 let keyboard_focused_window = get_window(&mut state, &surface.id());
1674 state.keyboard_focused_window = None;
1675 state.enter_token.take();
1676 // Prevent keyboard events from repeating after opening e.g. a file chooser and closing it quickly
1677 state.repeat.current_id += 1;
1678 state.restore_cursor_after_hide();
1679
1680 if let Some(window) = keyboard_focused_window {
1681 if let Some(ref mut compose) = state.compose_state {
1682 compose.reset();
1683 }
1684 state.pre_edit_text.take();
1685 drop(state);
1686 window.handle_ime(ImeInput::DeleteText);
1687 window.set_focused(false);
1688 }
1689 }
1690 wl_keyboard::Event::Modifiers {
1691 mods_depressed,
1692 mods_latched,
1693 mods_locked,
1694 group,
1695 ..
1696 } => {
1697 let focused_window = state.keyboard_focused_window.clone();
1698
1699 let keymap_state = state.keymap_state.as_mut().unwrap();
1700 let old_layout =
1701 keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
1702 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
1703 state.modifiers = modifiers_from_xkb(keymap_state);
1704 let keymap_state = state.keymap_state.as_mut().unwrap();
1705 state.capslock = capslock_from_xkb(keymap_state);
1706
1707 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1708 modifiers: state.modifiers,
1709 capslock: state.capslock,
1710 });
1711 drop(state);
1712
1713 if let Some(focused_window) = focused_window {
1714 focused_window.handle_input(input);
1715 }
1716
1717 if group != old_layout {
1718 this.handle_keyboard_layout_change();
1719 }
1720 }
1721 wl_keyboard::Event::Key {
1722 serial,
1723 key,
1724 state: WEnum::Value(key_state),
1725 ..
1726 } => {
1727 if key_state == wl_keyboard::KeyState::Pressed {
1728 state.serial_tracker.update(SerialKind::KeyPress, serial);
1729 }
1730
1731 let focused_window = state.keyboard_focused_window.clone();
1732 let Some(focused_window) = focused_window else {
1733 return;
1734 };
1735
1736 let keymap_state = state.keymap_state.as_ref().unwrap();
1737 let keycode = Keycode::from(key + MIN_KEYCODE);
1738 let keysym = keymap_state.key_get_one_sym(keycode);
1739
1740 match key_state {
1741 wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
1742 let mut keystroke =
1743 keystroke_from_xkb(keymap_state, state.modifiers, keycode);
1744 if let Some(mut compose) = state.compose_state.take() {
1745 compose.feed(keysym);
1746 match compose.status() {
1747 xkb::Status::Composing => {
1748 keystroke.key_char = None;
1749 state.pre_edit_text =
1750 compose.utf8().or(keystroke_underlying_dead_key(keysym));
1751 let pre_edit =
1752 state.pre_edit_text.clone().unwrap_or(String::default());
1753 drop(state);
1754 focused_window.handle_ime(ImeInput::SetMarkedText(pre_edit));
1755 state = client.borrow_mut();
1756 }
1757
1758 xkb::Status::Composed => {
1759 state.pre_edit_text.take();
1760 keystroke.key_char = compose.utf8();
1761 if let Some(keysym) = compose.keysym() {
1762 keystroke.key = xkb::keysym_get_name(keysym);
1763 }
1764 }
1765 xkb::Status::Cancelled => {
1766 let pre_edit = state.pre_edit_text.take();
1767 let new_pre_edit = keystroke_underlying_dead_key(keysym);
1768 state.pre_edit_text = new_pre_edit.clone();
1769 drop(state);
1770 if let Some(pre_edit) = pre_edit {
1771 focused_window.handle_ime(ImeInput::InsertText(pre_edit));
1772 }
1773 if let Some(current_key) = new_pre_edit {
1774 focused_window
1775 .handle_ime(ImeInput::SetMarkedText(current_key));
1776 }
1777 compose.feed(keysym);
1778 state = client.borrow_mut();
1779 }
1780 _ => {}
1781 }
1782 state.compose_state = Some(compose);
1783 }
1784 let input = PlatformInput::KeyDown(KeyDownEvent {
1785 keystroke: keystroke.clone(),
1786 is_held: false,
1787 prefer_character_input: false,
1788 });
1789
1790 state.repeat.current_id += 1;
1791 state.repeat.current_keycode = Some(keycode);
1792
1793 let rate = state.repeat.characters_per_second;
1794 let repeat_interval = Duration::from_secs(1) / rate.max(1);
1795 let id = state.repeat.current_id;
1796 state
1797 .loop_handle
1798 .insert_source(Timer::from_duration(state.repeat.delay), {
1799 let input = PlatformInput::KeyDown(KeyDownEvent {
1800 keystroke,
1801 is_held: true,
1802 prefer_character_input: false,
1803 });
1804 move |event_timestamp, _metadata, this| {
1805 let client = this.get_client();
1806 let state = client.borrow();
1807 let is_repeating = id == state.repeat.current_id
1808 && state.repeat.current_keycode.is_some()
1809 && state.keyboard_focused_window.is_some();
1810
1811 if !is_repeating || rate == 0 {
1812 return TimeoutAction::Drop;
1813 }
1814
1815 let focused_window =
1816 state.keyboard_focused_window.as_ref().unwrap().clone();
1817
1818 drop(state);
1819 focused_window.handle_input(input.clone());
1820
1821 // If the new scheduled time is in the past the event will repeat as soon as possible
1822 TimeoutAction::ToInstant(event_timestamp + repeat_interval)
1823 }
1824 })
1825 .unwrap();
1826
1827 drop(state);
1828 focused_window.handle_input(input);
1829 }
1830 wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
1831 let input = PlatformInput::KeyUp(KeyUpEvent {
1832 keystroke: keystroke_from_xkb(keymap_state, state.modifiers, keycode),
1833 });
1834
1835 if state.repeat.current_keycode == Some(keycode) {
1836 state.repeat.current_keycode = None;
1837 }
1838
1839 drop(state);
1840 focused_window.handle_input(input);
1841 }
1842 _ => {}
1843 }
1844 }
1845 _ => {}
1846 }
1847 }
1848}
1849
1850impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
1851 fn event(
1852 this: &mut Self,
1853 text_input: &zwp_text_input_v3::ZwpTextInputV3,
1854 event: <zwp_text_input_v3::ZwpTextInputV3 as Proxy>::Event,
1855 _: &(),
1856 _: &Connection,
1857 _: &QueueHandle<Self>,
1858 ) {
1859 let client = this.get_client();
1860 let mut state = client.borrow_mut();
1861 match event {
1862 zwp_text_input_v3::Event::Enter { .. } => {
1863 drop(state);
1864 this.enable_ime();
1865 }
1866 zwp_text_input_v3::Event::Leave { .. } => {
1867 drop(state);
1868 this.disable_ime();
1869 }
1870 zwp_text_input_v3::Event::CommitString { text } => {
1871 state.composing = false;
1872 let Some(window) = state.keyboard_focused_window.clone() else {
1873 return;
1874 };
1875
1876 if let Some(commit_text) = text {
1877 drop(state);
1878 // IBus Intercepts keys like `a`, `b`, but those keys are needed for vim mode.
1879 // We should only send ASCII characters to Zed, otherwise a user could remap a letter like `か` or `相`.
1880 if commit_text.len() == 1 {
1881 window.handle_input(PlatformInput::KeyDown(KeyDownEvent {
1882 keystroke: Keystroke {
1883 modifiers: Modifiers::default(),
1884 key: commit_text.clone(),
1885 key_char: Some(commit_text),
1886 },
1887 is_held: false,
1888 prefer_character_input: false,
1889 }));
1890 } else {
1891 window.handle_ime(ImeInput::InsertText(commit_text));
1892 }
1893 }
1894 }
1895 zwp_text_input_v3::Event::PreeditString { text, .. } => {
1896 state.composing = true;
1897 state.ime_pre_edit = text;
1898 }
1899 zwp_text_input_v3::Event::Done { serial } => {
1900 let last_serial = state.serial_tracker.get(SerialKind::InputMethod);
1901 state.serial_tracker.update(SerialKind::InputMethod, serial);
1902 let Some(window) = state.keyboard_focused_window.clone() else {
1903 return;
1904 };
1905
1906 if let Some(text) = state.ime_pre_edit.take() {
1907 drop(state);
1908 window.handle_ime(ImeInput::SetMarkedText(text));
1909 if let Some(area) = window.get_ime_area() {
1910 let mut state = client.borrow_mut();
1911 set_ime_cursor_rectangle_after_done(
1912 text_input,
1913 &mut state.last_ime_cursor_rectangle,
1914 area,
1915 last_serial.as_raw() == serial,
1916 );
1917 }
1918 } else {
1919 state.composing = false;
1920 drop(state);
1921 window.handle_ime(ImeInput::DeleteText);
1922 }
1923 }
1924 _ => {}
1925 }
1926 }
1927}
1928
1929fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
1930 // These values are coming from <linux/input-event-codes.h>.
1931 const BTN_LEFT: u32 = 0x110;
1932 const BTN_RIGHT: u32 = 0x111;
1933 const BTN_MIDDLE: u32 = 0x112;
1934 const BTN_SIDE: u32 = 0x113;
1935 const BTN_EXTRA: u32 = 0x114;
1936 const BTN_FORWARD: u32 = 0x115;
1937 const BTN_BACK: u32 = 0x116;
1938
1939 Some(match button {
1940 BTN_LEFT => MouseButton::Left,
1941 BTN_RIGHT => MouseButton::Right,
1942 BTN_MIDDLE => MouseButton::Middle,
1943 BTN_BACK | BTN_SIDE => MouseButton::Navigate(NavigationDirection::Back),
1944 BTN_FORWARD | BTN_EXTRA => MouseButton::Navigate(NavigationDirection::Forward),
1945 _ => return None,
1946 })
1947}
1948
1949impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
1950 fn event(
1951 this: &mut Self,
1952 wl_pointer: &wl_pointer::WlPointer,
1953 event: wl_pointer::Event,
1954 _: &(),
1955 _: &Connection,
1956 _: &QueueHandle<Self>,
1957 ) {
1958 let client = this.get_client();
1959 let mut state = client.borrow_mut();
1960
1961 match event {
1962 wl_pointer::Event::Enter {
1963 serial,
1964 surface,
1965 surface_x,
1966 surface_y,
1967 ..
1968 } => {
1969 let position = point(px(surface_x as f32), px(surface_y as f32));
1970 state.serial_tracker.update(SerialKind::MouseEnter, serial);
1971 state.mouse_location = Some(position);
1972 state.button_pressed = None;
1973
1974 if let Some(window) = get_window(&mut state, &surface.id()) {
1975 state.mouse_focused_window = Some(window.clone());
1976
1977 if state.enter_token.is_some() {
1978 state.enter_token = None;
1979 }
1980 state.restore_cursor_after_hide();
1981 if let Some(style) = state.cursor_style {
1982 if let Some(cursor_shape_device) = &state.cursor_shape_device {
1983 cursor_shape_device.set_shape(serial, to_shape(style));
1984 } else {
1985 let scale = window.primary_output_scale();
1986 state.cursor.set_icon(
1987 wl_pointer,
1988 serial,
1989 cursor_style_to_icon_names(style),
1990 scale,
1991 );
1992 }
1993 }
1994 let modifiers = state.modifiers;
1995 drop(state);
1996 window.set_hovered(true);
1997 // No Motion follows Enter unless the pointer keeps moving, so synthesize
1998 // a MouseMove to establish hover at the entry position.
1999 window.handle_input(PlatformInput::MouseMove(MouseMoveEvent {
2000 position,
2001 pressed_button: None,
2002 modifiers,
2003 }));
2004 }
2005 }
2006 wl_pointer::Event::Leave { .. } => {
2007 if let Some(focused_window) = state.mouse_focused_window.clone() {
2008 let input = PlatformInput::MouseExited(MouseExitEvent {
2009 position: state.mouse_location.unwrap(),
2010 pressed_button: state.button_pressed,
2011 modifiers: state.modifiers,
2012 });
2013 state.mouse_focused_window = None;
2014 state.mouse_location = None;
2015 state.button_pressed = None;
2016 state.cursor_hidden_window = None;
2017
2018 drop(state);
2019 focused_window.handle_input(input);
2020 focused_window.set_hovered(false);
2021 }
2022 }
2023 wl_pointer::Event::Motion {
2024 surface_x,
2025 surface_y,
2026 ..
2027 } => {
2028 if state.mouse_focused_window.is_none() {
2029 return;
2030 }
2031 state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
2032 state.restore_cursor_after_hide();
2033
2034 if let Some(window) = state.mouse_focused_window.clone() {
2035 if window.is_blocked() {
2036 let default_style = CursorStyle::Arrow;
2037 if state.cursor_style != Some(default_style) {
2038 let serial = state.serial_tracker.get(SerialKind::MouseEnter);
2039 state.cursor_style = Some(default_style);
2040
2041 if let Some(cursor_shape_device) = &state.cursor_shape_device {
2042 cursor_shape_device
2043 .set_shape(serial.as_raw(), to_shape(default_style));
2044 } else {
2045 // cursor-shape-v1 isn't supported, set the cursor using a surface.
2046 let wl_pointer = state
2047 .wl_pointer
2048 .clone()
2049 .expect("window is focused by pointer");
2050 let scale = window.primary_output_scale();
2051 state.cursor.set_icon(
2052 &wl_pointer,
2053 serial.as_raw(),
2054 cursor_style_to_icon_names(default_style),
2055 scale,
2056 );
2057 }
2058 }
2059 }
2060 if state
2061 .keyboard_focused_window
2062 .as_ref()
2063 .is_some_and(|keyboard_window| window.ptr_eq(keyboard_window))
2064 {
2065 state.enter_token = None;
2066 }
2067 let input = PlatformInput::MouseMove(MouseMoveEvent {
2068 position: state.mouse_location.unwrap(),
2069 pressed_button: state.button_pressed,
2070 modifiers: state.modifiers,
2071 });
2072 drop(state);
2073 window.handle_input(input);
2074 }
2075 }
2076 wl_pointer::Event::Button {
2077 serial,
2078 button,
2079 state: WEnum::Value(button_state),
2080 ..
2081 } => {
2082 // Record presses only. Requests referencing this serial (popup grabs,
2083 // interactive moves) are declined when given a release serial.
2084 if button_state == wl_pointer::ButtonState::Pressed {
2085 state.serial_tracker.update(SerialKind::MousePress, serial);
2086 }
2087 let button = linux_button_to_gpui(button);
2088 let Some(button) = button else { return };
2089 if state.mouse_focused_window.is_none() {
2090 return;
2091 }
2092 match button_state {
2093 wl_pointer::ButtonState::Pressed => {
2094 if let Some(window) = state.keyboard_focused_window.clone() {
2095 if state.composing && state.text_input.is_some() {
2096 drop(state);
2097 // text_input_v3 don't have something like a reset function
2098 this.disable_ime();
2099 this.enable_ime();
2100 window.handle_ime(ImeInput::UnmarkText);
2101 state = client.borrow_mut();
2102 } else if let (Some(text), Some(compose)) =
2103 (state.pre_edit_text.take(), state.compose_state.as_mut())
2104 {
2105 compose.reset();
2106 drop(state);
2107 window.handle_ime(ImeInput::InsertText(text));
2108 state = client.borrow_mut();
2109 }
2110 }
2111 let click_elapsed = state.click.last_click.elapsed();
2112
2113 if click_elapsed < DOUBLE_CLICK_INTERVAL
2114 && state
2115 .click
2116 .last_mouse_button
2117 .is_some_and(|prev_button| prev_button == button)
2118 && is_within_click_distance(
2119 state.click.last_location,
2120 state.mouse_location.unwrap(),
2121 )
2122 {
2123 state.click.current_count += 1;
2124 } else {
2125 state.click.current_count = 1;
2126 }
2127
2128 state.click.last_click = Instant::now();
2129 state.click.last_mouse_button = Some(button);
2130 state.click.last_location = state.mouse_location.unwrap();
2131
2132 state.button_pressed = Some(button);
2133
2134 if let Some(window) = state.mouse_focused_window.clone() {
2135 let input = PlatformInput::MouseDown(MouseDownEvent {
2136 button,
2137 position: state.mouse_location.unwrap(),
2138 modifiers: state.modifiers,
2139 click_count: state.click.current_count,
2140 first_mouse: state.enter_token.take().is_some(),
2141 });
2142 drop(state);
2143 window.handle_input(input);
2144 }
2145 }
2146 wl_pointer::ButtonState::Released => {
2147 state.button_pressed = None;
2148
2149 if let Some(window) = state.mouse_focused_window.clone() {
2150 let input = PlatformInput::MouseUp(MouseUpEvent {
2151 button,
2152 position: state.mouse_location.unwrap(),
2153 modifiers: state.modifiers,
2154 click_count: state.click.current_count,
2155 });
2156 drop(state);
2157 window.handle_input(input);
2158 }
2159 }
2160 _ => {}
2161 }
2162 }
2163
2164 // Axis Events
2165 wl_pointer::Event::AxisSource {
2166 axis_source: WEnum::Value(axis_source),
2167 } => {
2168 state.axis_source = axis_source;
2169 }
2170 wl_pointer::Event::Axis {
2171 axis: WEnum::Value(axis),
2172 value,
2173 ..
2174 } => {
2175 if state.axis_source == AxisSource::Wheel {
2176 return;
2177 }
2178 let axis = if state.modifiers.shift {
2179 wl_pointer::Axis::HorizontalScroll
2180 } else {
2181 axis
2182 };
2183 let axis_modifier = match axis {
2184 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
2185 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
2186 _ => 1.0,
2187 };
2188 state.scroll_event_received = true;
2189 let scroll_delta = state
2190 .continuous_scroll_delta
2191 .get_or_insert(point(px(0.0), px(0.0)));
2192 let modifier = 3.0;
2193 match axis {
2194 wl_pointer::Axis::VerticalScroll => {
2195 scroll_delta.y += px(value as f32 * modifier * axis_modifier);
2196 }
2197 wl_pointer::Axis::HorizontalScroll => {
2198 scroll_delta.x += px(value as f32 * modifier * axis_modifier);
2199 }
2200 _ => unreachable!(),
2201 }
2202 }
2203 wl_pointer::Event::AxisDiscrete {
2204 axis: WEnum::Value(axis),
2205 discrete,
2206 } => {
2207 state.scroll_event_received = true;
2208 let axis = if state.modifiers.shift {
2209 wl_pointer::Axis::HorizontalScroll
2210 } else {
2211 axis
2212 };
2213 let axis_modifier = match axis {
2214 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
2215 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
2216 _ => 1.0,
2217 };
2218
2219 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
2220 match axis {
2221 wl_pointer::Axis::VerticalScroll => {
2222 scroll_delta.y += discrete as f32 * axis_modifier * SCROLL_LINES;
2223 }
2224 wl_pointer::Axis::HorizontalScroll => {
2225 scroll_delta.x += discrete as f32 * axis_modifier * SCROLL_LINES;
2226 }
2227 _ => unreachable!(),
2228 }
2229 }
2230 wl_pointer::Event::AxisValue120 {
2231 axis: WEnum::Value(axis),
2232 value120,
2233 } => {
2234 state.scroll_event_received = true;
2235 let axis = if state.modifiers.shift {
2236 wl_pointer::Axis::HorizontalScroll
2237 } else {
2238 axis
2239 };
2240 let axis_modifier = match axis {
2241 wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
2242 wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
2243 _ => unreachable!(),
2244 };
2245
2246 let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
2247 let wheel_percent = value120 as f32 / 120.0;
2248 match axis {
2249 wl_pointer::Axis::VerticalScroll => {
2250 scroll_delta.y += wheel_percent * axis_modifier * SCROLL_LINES;
2251 }
2252 wl_pointer::Axis::HorizontalScroll => {
2253 scroll_delta.x += wheel_percent * axis_modifier * SCROLL_LINES;
2254 }
2255 _ => unreachable!(),
2256 }
2257 }
2258 wl_pointer::Event::Frame => {
2259 if state.scroll_event_received {
2260 state.scroll_event_received = false;
2261 let continuous = state.continuous_scroll_delta.take();
2262 let discrete = state.discrete_scroll_delta.take();
2263 if let Some(continuous) = continuous {
2264 if let Some(window) = state.mouse_focused_window.clone() {
2265 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
2266 position: state.mouse_location.unwrap(),
2267 delta: ScrollDelta::Pixels(continuous),
2268 modifiers: state.modifiers,
2269 touch_phase: TouchPhase::Moved,
2270 });
2271 drop(state);
2272 window.handle_input(input);
2273 }
2274 } else if let Some(discrete) = discrete
2275 && let Some(window) = state.mouse_focused_window.clone()
2276 {
2277 let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
2278 position: state.mouse_location.unwrap(),
2279 delta: ScrollDelta::Lines(discrete),
2280 modifiers: state.modifiers,
2281 touch_phase: TouchPhase::Moved,
2282 });
2283 drop(state);
2284 window.handle_input(input);
2285 }
2286 }
2287 }
2288 _ => {}
2289 }
2290 }
2291}
2292
2293impl Dispatch<zwp_pointer_gestures_v1::ZwpPointerGesturesV1, ()> for WaylandClientStatePtr {
2294 fn event(
2295 _this: &mut Self,
2296 _: &zwp_pointer_gestures_v1::ZwpPointerGesturesV1,
2297 _: <zwp_pointer_gestures_v1::ZwpPointerGesturesV1 as Proxy>::Event,
2298 _: &(),
2299 _: &Connection,
2300 _: &QueueHandle<Self>,
2301 ) {
2302 // The gesture manager doesn't generate events
2303 }
2304}
2305
2306impl Dispatch<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1, ()>
2307 for WaylandClientStatePtr
2308{
2309 fn event(
2310 this: &mut Self,
2311 _: &zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1,
2312 event: <zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1 as Proxy>::Event,
2313 _: &(),
2314 _: &Connection,
2315 _: &QueueHandle<Self>,
2316 ) {
2317 use gpui::PinchEvent;
2318
2319 let client = this.get_client();
2320 let mut state = client.borrow_mut();
2321
2322 let Some(window) = state.mouse_focused_window.clone() else {
2323 return;
2324 };
2325
2326 match event {
2327 zwp_pointer_gesture_pinch_v1::Event::Begin {
2328 serial: _,
2329 time: _,
2330 surface: _,
2331 fingers: _,
2332 } => {
2333 state.pinch_scale = 1.0;
2334 let input = PlatformInput::Pinch(PinchEvent {
2335 position: state.mouse_location.unwrap_or(point(px(0.0), px(0.0))),
2336 delta: 0.0,
2337 modifiers: state.modifiers,
2338 phase: TouchPhase::Started,
2339 });
2340 drop(state);
2341 window.handle_input(input);
2342 }
2343 zwp_pointer_gesture_pinch_v1::Event::Update { time: _, scale, .. } => {
2344 let new_absolute_scale = scale as f32;
2345 let previous_scale = state.pinch_scale;
2346 let zoom_delta = new_absolute_scale - previous_scale;
2347 state.pinch_scale = new_absolute_scale;
2348
2349 let input = PlatformInput::Pinch(PinchEvent {
2350 position: state.mouse_location.unwrap_or(point(px(0.0), px(0.0))),
2351 delta: zoom_delta,
2352 modifiers: state.modifiers,
2353 phase: TouchPhase::Moved,
2354 });
2355 drop(state);
2356 window.handle_input(input);
2357 }
2358 zwp_pointer_gesture_pinch_v1::Event::End {
2359 serial: _,
2360 time: _,
2361 cancelled: _,
2362 } => {
2363 state.pinch_scale = 1.0;
2364 let input = PlatformInput::Pinch(PinchEvent {
2365 position: state.mouse_location.unwrap_or(point(px(0.0), px(0.0))),
2366 delta: 0.0,
2367 modifiers: state.modifiers,
2368 phase: TouchPhase::Ended,
2369 });
2370 drop(state);
2371 window.handle_input(input);
2372 }
2373 _ => {}
2374 }
2375 }
2376}
2377
2378impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientStatePtr {
2379 fn event(
2380 this: &mut Self,
2381 _: &wp_fractional_scale_v1::WpFractionalScaleV1,
2382 event: <wp_fractional_scale_v1::WpFractionalScaleV1 as Proxy>::Event,
2383 surface_id: &ObjectId,
2384 _: &Connection,
2385 _: &QueueHandle<Self>,
2386 ) {
2387 let client = this.get_client();
2388 let mut state = client.borrow_mut();
2389
2390 let Some(window) = get_window(&mut state, surface_id) else {
2391 return;
2392 };
2393
2394 drop(state);
2395 window.handle_fractional_scale_event(event);
2396 }
2397}
2398
2399impl Dispatch<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ObjectId>
2400 for WaylandClientStatePtr
2401{
2402 fn event(
2403 this: &mut Self,
2404 _: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1,
2405 event: zxdg_toplevel_decoration_v1::Event,
2406 surface_id: &ObjectId,
2407 _: &Connection,
2408 _: &QueueHandle<Self>,
2409 ) {
2410 let client = this.get_client();
2411 let mut state = client.borrow_mut();
2412 let Some(window) = get_window(&mut state, surface_id) else {
2413 return;
2414 };
2415
2416 drop(state);
2417 window.handle_toplevel_decoration_event(event);
2418 }
2419}
2420
2421impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
2422 fn event(
2423 this: &mut Self,
2424 _: &wl_data_device::WlDataDevice,
2425 event: wl_data_device::Event,
2426 _: &(),
2427 _: &Connection,
2428 _: &QueueHandle<Self>,
2429 ) {
2430 let client = this.get_client();
2431 let mut state = client.borrow_mut();
2432
2433 match event {
2434 // Clipboard
2435 wl_data_device::Event::DataOffer { id: data_offer } => {
2436 state.data_offers.push(DataOffer::new(data_offer));
2437 if state.data_offers.len() > 2 {
2438 // At most we store a clipboard offer and a drag and drop offer.
2439 state.data_offers.remove(0).inner.destroy();
2440 }
2441 }
2442 wl_data_device::Event::Selection { id: data_offer } => {
2443 if let Some(offer) = data_offer {
2444 let offer = state
2445 .data_offers
2446 .iter()
2447 .find(|wrapper| wrapper.inner.id() == offer.id());
2448 let offer = offer.cloned();
2449 state.clipboard.set_offer(offer);
2450 } else {
2451 state.clipboard.set_offer(None);
2452 }
2453 }
2454
2455 // Drag and drop
2456 wl_data_device::Event::Enter {
2457 serial,
2458 surface,
2459 x,
2460 y,
2461 id: data_offer,
2462 } => {
2463 state.serial_tracker.update(SerialKind::DataDevice, serial);
2464 if let Some(data_offer) = data_offer {
2465 let Some(drag_window) = get_window(&mut state, &surface.id()) else {
2466 return;
2467 };
2468
2469 const ACTIONS: DndAction = DndAction::Copy;
2470 data_offer.set_actions(ACTIONS, ACTIONS);
2471
2472 let pipe = Pipe::new().unwrap();
2473 data_offer.receive(FILE_LIST_MIME_TYPE.to_string(), unsafe {
2474 BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
2475 });
2476 let fd = pipe.read;
2477 drop(pipe.write);
2478
2479 let read_task = state.common.background_executor.spawn(async {
2480 let buffer = read_fd_with_timeout(fd, PIPE_READ_TIMEOUT)?;
2481 let text = String::from_utf8(buffer)?;
2482 anyhow::Ok(text)
2483 });
2484
2485 let this = this.clone();
2486 state
2487 .common
2488 .foreground_executor
2489 .spawn(async move {
2490 let file_list = match read_task.await {
2491 Ok(list) => list,
2492 Err(err) => {
2493 log::error!("error reading drag and drop pipe: {err:?}");
2494 return;
2495 }
2496 };
2497
2498 let paths: SmallVec<[_; 2]> = file_list
2499 .lines()
2500 .filter_map(|path| Url::parse(path).log_err())
2501 .filter_map(|url| match url.to_file_path() {
2502 Ok(url) => Some(url),
2503 Err(()) => {
2504 log::error!("Failed turn {url:?} into a file path");
2505 None
2506 }
2507 })
2508 .collect();
2509 let position = Point::new(x.into(), y.into());
2510
2511 // Prevent dropping text from other programs.
2512 if paths.is_empty() {
2513 data_offer.destroy();
2514 return;
2515 }
2516
2517 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
2518 position,
2519 paths: gpui::ExternalPaths(paths),
2520 });
2521
2522 let client = this.get_client();
2523 let mut state = client.borrow_mut();
2524 state.drag.data_offer = Some(data_offer);
2525 state.drag.window = Some(drag_window.clone());
2526 state.drag.position = position;
2527
2528 drop(state);
2529 drag_window.handle_input(input);
2530 })
2531 .detach();
2532 }
2533 }
2534 wl_data_device::Event::Motion { x, y, .. } => {
2535 let Some(drag_window) = state.drag.window.clone() else {
2536 return;
2537 };
2538 let position = Point::new(x.into(), y.into());
2539 state.drag.position = position;
2540
2541 let input = PlatformInput::FileDrop(FileDropEvent::Pending { position });
2542 drop(state);
2543 drag_window.handle_input(input);
2544 }
2545 wl_data_device::Event::Leave => {
2546 let Some(drag_window) = state.drag.window.clone() else {
2547 return;
2548 };
2549 let data_offer = state.drag.data_offer.clone().unwrap();
2550 data_offer.destroy();
2551
2552 state.drag.data_offer = None;
2553 state.drag.window = None;
2554
2555 let input = PlatformInput::FileDrop(FileDropEvent::Exited {});
2556 drop(state);
2557 drag_window.handle_input(input);
2558 }
2559 wl_data_device::Event::Drop => {
2560 let Some(drag_window) = state.drag.window.clone() else {
2561 return;
2562 };
2563 let data_offer = state.drag.data_offer.clone().unwrap();
2564 data_offer.finish();
2565 data_offer.destroy();
2566
2567 state.drag.data_offer = None;
2568 state.drag.window = None;
2569
2570 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
2571 position: state.drag.position,
2572 });
2573 drop(state);
2574 drag_window.handle_input(input);
2575 }
2576 _ => {}
2577 }
2578 }
2579
2580 event_created_child!(WaylandClientStatePtr, wl_data_device::WlDataDevice, [
2581 wl_data_device::EVT_DATA_OFFER_OPCODE => (wl_data_offer::WlDataOffer, ()),
2582 ]);
2583}
2584
2585impl Dispatch<wl_data_offer::WlDataOffer, ()> for WaylandClientStatePtr {
2586 fn event(
2587 this: &mut Self,
2588 data_offer: &wl_data_offer::WlDataOffer,
2589 event: wl_data_offer::Event,
2590 _: &(),
2591 _: &Connection,
2592 _: &QueueHandle<Self>,
2593 ) {
2594 let client = this.get_client();
2595 let mut state = client.borrow_mut();
2596
2597 if let wl_data_offer::Event::Offer { mime_type } = event {
2598 // Drag and drop
2599 if mime_type == FILE_LIST_MIME_TYPE {
2600 let serial = state.serial_tracker.get(SerialKind::DataDevice);
2601 let mime_type = mime_type.clone();
2602 data_offer.accept(serial.as_raw(), Some(mime_type));
2603 }
2604
2605 // Clipboard
2606 if let Some(offer) = state
2607 .data_offers
2608 .iter_mut()
2609 .find(|wrapper| wrapper.inner.id() == data_offer.id())
2610 {
2611 offer.add_mime_type(mime_type);
2612 }
2613 }
2614 }
2615}
2616
2617impl Dispatch<wl_data_source::WlDataSource, ()> for WaylandClientStatePtr {
2618 fn event(
2619 this: &mut Self,
2620 data_source: &wl_data_source::WlDataSource,
2621 event: wl_data_source::Event,
2622 _: &(),
2623 _: &Connection,
2624 _: &QueueHandle<Self>,
2625 ) {
2626 let client = this.get_client();
2627 let state = client.borrow_mut();
2628
2629 match event {
2630 wl_data_source::Event::Send { mime_type, fd } => {
2631 state.clipboard.send(mime_type, fd);
2632 }
2633 wl_data_source::Event::Cancelled => {
2634 data_source.destroy();
2635 }
2636 _ => {}
2637 }
2638 }
2639}
2640
2641impl Dispatch<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, ()>
2642 for WaylandClientStatePtr
2643{
2644 fn event(
2645 this: &mut Self,
2646 _: &zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1,
2647 event: zwp_primary_selection_device_v1::Event,
2648 _: &(),
2649 _: &Connection,
2650 _: &QueueHandle<Self>,
2651 ) {
2652 let client = this.get_client();
2653 let mut state = client.borrow_mut();
2654
2655 match event {
2656 zwp_primary_selection_device_v1::Event::DataOffer { offer } => {
2657 let old_offer = state.primary_data_offer.replace(DataOffer::new(offer));
2658 if let Some(old_offer) = old_offer {
2659 old_offer.inner.destroy();
2660 }
2661 }
2662 zwp_primary_selection_device_v1::Event::Selection { id: data_offer } => {
2663 if data_offer.is_some() {
2664 let offer = state.primary_data_offer.clone();
2665 state.clipboard.set_primary_offer(offer);
2666 } else {
2667 state.clipboard.set_primary_offer(None);
2668 }
2669 }
2670 _ => {}
2671 }
2672 }
2673
2674 event_created_child!(WaylandClientStatePtr, zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, [
2675 zwp_primary_selection_device_v1::EVT_DATA_OFFER_OPCODE => (zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, ()),
2676 ]);
2677}
2678
2679impl Dispatch<zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, ()>
2680 for WaylandClientStatePtr
2681{
2682 fn event(
2683 this: &mut Self,
2684 _data_offer: &zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1,
2685 event: zwp_primary_selection_offer_v1::Event,
2686 _: &(),
2687 _: &Connection,
2688 _: &QueueHandle<Self>,
2689 ) {
2690 let client = this.get_client();
2691 let mut state = client.borrow_mut();
2692
2693 if let zwp_primary_selection_offer_v1::Event::Offer { mime_type } = event
2694 && let Some(offer) = state.primary_data_offer.as_mut()
2695 {
2696 offer.add_mime_type(mime_type);
2697 }
2698 }
2699}
2700
2701impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()>
2702 for WaylandClientStatePtr
2703{
2704 fn event(
2705 this: &mut Self,
2706 selection_source: &zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1,
2707 event: zwp_primary_selection_source_v1::Event,
2708 _: &(),
2709 _: &Connection,
2710 _: &QueueHandle<Self>,
2711 ) {
2712 let client = this.get_client();
2713 let state = client.borrow_mut();
2714
2715 match event {
2716 zwp_primary_selection_source_v1::Event::Send { mime_type, fd } => {
2717 state.clipboard.send_primary(mime_type, fd);
2718 }
2719 zwp_primary_selection_source_v1::Event::Cancelled => {
2720 selection_source.destroy();
2721 }
2722 _ => {}
2723 }
2724 }
2725}
2726
2727impl Dispatch<XdgWmDialogV1, ()> for WaylandClientStatePtr {
2728 fn event(
2729 _: &mut Self,
2730 _: &XdgWmDialogV1,
2731 _: <XdgWmDialogV1 as Proxy>::Event,
2732 _: &(),
2733 _: &Connection,
2734 _: &QueueHandle<Self>,
2735 ) {
2736 }
2737}
2738
2739impl Dispatch<XdgDialogV1, ()> for WaylandClientStatePtr {
2740 fn event(
2741 _state: &mut Self,
2742 _proxy: &XdgDialogV1,
2743 _event: <XdgDialogV1 as Proxy>::Event,
2744 _data: &(),
2745 _conn: &Connection,
2746 _qhandle: &QueueHandle<Self>,
2747 ) {
2748 }
2749}
2750
2751#[cfg(test)]
2752mod tests {
2753 use std::cell::Cell;
2754
2755 use super::*;
2756
2757 #[derive(Default)]
2758 struct FakeImeCursorRectangleSink {
2759 cursor_rectangles: RefCell<Vec<(i32, i32, i32, i32)>>,
2760 commit_count: Cell<usize>,
2761 }
2762
2763 impl ImeCursorRectangleSink for FakeImeCursorRectangleSink {
2764 fn set_ime_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32) {
2765 self.cursor_rectangles
2766 .borrow_mut()
2767 .push((x, y, width, height));
2768 }
2769
2770 fn commit_ime_state(&self) {
2771 self.commit_count.set(self.commit_count.get() + 1);
2772 }
2773 }
2774
2775 fn ime_cursor_bounds(x: f32) -> Bounds<Pixels> {
2776 Bounds::new(point(px(x), px(20.25)), size(px(1.0), px(18.75)))
2777 }
2778
2779 #[test]
2780 fn caches_cursor_rectangle_committed_after_done() {
2781 let text_input = FakeImeCursorRectangleSink::default();
2782 let mut last_ime_cursor_rectangle = None;
2783 let initial_bounds = ime_cursor_bounds(10.0);
2784 let updated_bounds = ime_cursor_bounds(20.0);
2785
2786 update_ime_cursor_rectangle(&text_input, &mut last_ime_cursor_rectangle, initial_bounds);
2787 set_ime_cursor_rectangle_after_done(
2788 &text_input,
2789 &mut last_ime_cursor_rectangle,
2790 updated_bounds,
2791 true,
2792 );
2793 update_ime_cursor_rectangle(&text_input, &mut last_ime_cursor_rectangle, updated_bounds);
2794
2795 assert_eq!(text_input.commit_count.get(), 2);
2796 assert_eq!(text_input.cursor_rectangles.borrow().len(), 2);
2797 }
2798
2799 #[test]
2800 fn skips_unchanged_cursor_rectangle_after_done() {
2801 let text_input = FakeImeCursorRectangleSink::default();
2802 let mut last_ime_cursor_rectangle = None;
2803 let bounds = ime_cursor_bounds(10.0);
2804
2805 update_ime_cursor_rectangle(&text_input, &mut last_ime_cursor_rectangle, bounds);
2806 set_ime_cursor_rectangle_after_done(
2807 &text_input,
2808 &mut last_ime_cursor_rectangle,
2809 bounds,
2810 true,
2811 );
2812
2813 assert_eq!(text_input.commit_count.get(), 1);
2814 assert_eq!(text_input.cursor_rectangles.borrow().len(), 1);
2815 }
2816
2817 #[test]
2818 fn skips_cursor_rectangles_with_unchanged_protocol_coordinates() {
2819 let text_input = FakeImeCursorRectangleSink::default();
2820 let mut last_ime_cursor_rectangle = None;
2821
2822 update_ime_cursor_rectangle(
2823 &text_input,
2824 &mut last_ime_cursor_rectangle,
2825 ime_cursor_bounds(10.25),
2826 );
2827 update_ime_cursor_rectangle(
2828 &text_input,
2829 &mut last_ime_cursor_rectangle,
2830 ime_cursor_bounds(10.75),
2831 );
2832
2833 assert_eq!(text_input.commit_count.get(), 1);
2834 assert_eq!(
2835 text_input.cursor_rectangles.borrow().as_slice(),
2836 &[(10, 20, 1, 18)]
2837 );
2838 }
2839}
2840