use std::{ cell::{Ref, RefCell, RefMut}, ffi::c_void, ptr::NonNull, rc::Rc, sync::Arc, }; use collections::{FxHashSet, HashMap}; use futures::channel::oneshot::Receiver; use raw_window_handle as rwh; use wayland_backend::client::ObjectId; use wayland_client::WEnum; use wayland_client::{ Proxy, protocol::{wl_output, wl_surface}, }; use wayland_protocols::wp::viewporter::client::wp_viewport; use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1; use wayland_protocols::xdg::shell::client::xdg_surface; use wayland_protocols::xdg::shell::client::xdg_toplevel::{self}; use wayland_protocols::{ wp::fractional_scale::v1::client::wp_fractional_scale_v1, xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1, }; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur; use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1; use crate::linux::wayland::{display::WaylandDisplay, serial::SerialKind}; use crate::linux::{Globals, Output, WaylandClientStatePtr, get_window}; use gpui::{ AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, GpuSpecs, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px, size, }; use gpui_wgpu::{CompositorGpuHint, WgpuRenderer, WgpuSurfaceConfig, wgpu}; #[derive(Default)] pub(crate) struct Callbacks { request_frame: Option>, input: Option gpui::DispatchEventResult>>, active_status_change: Option>, hover_status_change: Option>, resize: Option, f32)>>, moved: Option>, should_close: Option bool>>, close: Option>, appearance_changed: Option>, button_layout_changed: Option>, } #[derive(Debug, Clone, Copy)] struct RawWindow { window: *mut c_void, display: *mut c_void, } // Safety: The raw pointers in RawWindow point to Wayland surface/display // which are valid for the window's lifetime. These are used only for // passing to wgpu which needs Send+Sync for surface creation. unsafe impl Send for RawWindow {} unsafe impl Sync for RawWindow {} impl rwh::HasWindowHandle for RawWindow { fn window_handle(&self) -> Result, rwh::HandleError> { let window = NonNull::new(self.window).unwrap(); let handle = rwh::WaylandWindowHandle::new(window); Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) }) } } impl rwh::HasDisplayHandle for RawWindow { fn display_handle(&self) -> Result, rwh::HandleError> { let display = NonNull::new(self.display).unwrap(); let handle = rwh::WaylandDisplayHandle::new(display); Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) } } #[derive(Debug)] struct InProgressConfigure { size: Option>, fullscreen: bool, maximized: bool, resizing: bool, tiling: Tiling, } pub struct WaylandWindowState { surface_state: WaylandSurfaceState, acknowledged_first_configure: bool, parent: Option, children: FxHashSet, pub surface: wl_surface::WlSurface, app_id: Option, appearance: WindowAppearance, blur: Option, viewport: Option, outputs: HashMap, display: Option<(ObjectId, Output)>, globals: Globals, renderer: WgpuRenderer, bounds: Bounds, scale: f32, input_handler: Option, decorations: WindowDecorations, background_appearance: WindowBackgroundAppearance, fullscreen: bool, maximized: bool, tiling: Tiling, window_bounds: Bounds, client: WaylandClientStatePtr, handle: AnyWindowHandle, active: bool, hovered: bool, pub(crate) force_render_after_recovery: bool, renderer_presented: bool, in_progress_configure: Option, resize_throttle: bool, in_progress_window_controls: Option, window_controls: WindowControls, client_inset: Option, accesskit_adapter: Option, } pub enum WaylandSurfaceState { Xdg(WaylandXdgSurfaceState), LayerShell(WaylandLayerSurfaceState), } impl WaylandSurfaceState { fn new( surface: &wl_surface::WlSurface, globals: &Globals, params: &WindowParams, parent: Option, target_output: Option, ) -> anyhow::Result { // For layer_shell windows, create a layer surface instead of an xdg surface if let WindowKind::LayerShell(options) = ¶ms.kind { let Some(layer_shell) = globals.layer_shell.as_ref() else { return Err(LayerShellNotSupportedError.into()); }; let layer_surface = layer_shell.get_layer_surface( &surface, target_output.as_ref(), super::layer_shell::wayland_layer(options.layer), options.namespace.clone(), &globals.qh, surface.id(), ); let width = f32::from(params.bounds.size.width); let height = f32::from(params.bounds.size.height); layer_surface.set_size(width as u32, height as u32); layer_surface.set_anchor(super::layer_shell::wayland_anchor(options.anchor)); layer_surface.set_keyboard_interactivity( super::layer_shell::wayland_keyboard_interactivity(options.keyboard_interactivity), ); if let Some(margin) = options.margin { layer_surface.set_margin( f32::from(margin.0) as i32, f32::from(margin.1) as i32, f32::from(margin.2) as i32, f32::from(margin.3) as i32, ) } if let Some(exclusive_zone) = options.exclusive_zone { layer_surface.set_exclusive_zone(f32::from(exclusive_zone) as i32); } if let Some(exclusive_edge) = options.exclusive_edge { layer_surface .set_exclusive_edge(super::layer_shell::wayland_anchor(exclusive_edge)); } return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, })); } // All other WindowKinds result in a regular xdg surface let xdg_surface = globals .wm_base .get_xdg_surface(&surface, &globals.qh, surface.id()); let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id()); let xdg_parent = parent.as_ref().and_then(|w| w.toplevel()); if params.kind == WindowKind::Floating || params.kind == WindowKind::Dialog { toplevel.set_parent(xdg_parent.as_ref()); } let dialog = if params.kind == WindowKind::Dialog { let dialog = globals.dialog.as_ref().map(|dialog| { let xdg_dialog = dialog.get_xdg_dialog(&toplevel, &globals.qh, ()); xdg_dialog.set_modal(); xdg_dialog }); if let Some(parent) = parent.as_ref() { parent.add_child(surface.id()); } dialog } else { None }; if let Some(size) = params.window_min_size { toplevel.set_min_size(f32::from(size.width) as i32, f32::from(size.height) as i32); } // Attempt to set up window decorations based on the requested configuration let decoration = globals .decoration_manager .as_ref() .map(|decoration_manager| { decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id()) }); Ok(WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, toplevel, decoration, dialog, })) } } pub struct WaylandXdgSurfaceState { xdg_surface: xdg_surface::XdgSurface, toplevel: xdg_toplevel::XdgToplevel, decoration: Option, dialog: Option, } pub struct WaylandLayerSurfaceState { layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, } impl WaylandSurfaceState { fn ack_configure(&self, serial: u32) { match self { WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, .. }) => { xdg_surface.ack_configure(serial); } WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) => { layer_surface.ack_configure(serial); } } } fn decoration(&self) -> Option<&zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1> { if let WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { decoration, .. }) = self { decoration.as_ref() } else { None } } fn toplevel(&self) -> Option<&xdg_toplevel::XdgToplevel> { if let WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { toplevel, .. }) = self { Some(toplevel) } else { None } } fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32) { match self { WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, .. }) => { xdg_surface.set_window_geometry(x, y, width, height); } WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) => { // cannot set window position of a layer surface layer_surface.set_size(width as u32, height as u32); } } } fn set_exclusive_zone(&self, zone: i32) { if let WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) = self { layer_surface.set_exclusive_zone(zone); } } fn destroy(&mut self) { match self { WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, toplevel, decoration: _decoration, dialog, }) => { // drop the dialog before toplevel so compositor can explicitly unapply it's effects if let Some(dialog) = dialog { dialog.destroy(); } // The role object (toplevel) must always be destroyed before the xdg_surface. // See https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy toplevel.destroy(); xdg_surface.destroy(); } WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface }) => { layer_surface.destroy(); } } } } #[derive(Clone)] pub struct WaylandWindowStatePtr { state: Rc>, callbacks: Rc>, } impl WaylandWindowState { pub(crate) fn new( handle: AnyWindowHandle, surface: wl_surface::WlSurface, surface_state: WaylandSurfaceState, appearance: WindowAppearance, viewport: Option, client: WaylandClientStatePtr, globals: Globals, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, gpu_requirements: Option, options: WindowParams, parent: Option, ) -> anyhow::Result { let renderer = { let raw_window = RawWindow { window: surface.id().as_ptr().cast::(), display: surface .backend() .upgrade() .unwrap() .display_ptr() .cast::(), }; let config = WgpuSurfaceConfig { size: Size { width: DevicePixels(f32::from(options.bounds.size.width) as i32), height: DevicePixels(f32::from(options.bounds.size.height) as i32), }, transparent: true, // Prefer Mailbox to avoid blocking. Falls back to FIFO if Mailbox is unsupported. preferred_present_mode: Some(wgpu::PresentMode::Mailbox), }; WgpuRenderer::new( gpu_context, &raw_window, config, compositor_gpu, gpu_requirements, )? }; if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state { if let Some(title) = options.titlebar.and_then(|titlebar| titlebar.title) { xdg_state.toplevel.set_title(title.to_string()); } // Set max window size based on the GPU's maximum texture dimension. // This prevents the window from being resized larger than what the GPU can render. let max_texture_size = renderer.max_texture_size() as i32; xdg_state .toplevel .set_max_size(max_texture_size, max_texture_size); } Ok(Self { surface_state, acknowledged_first_configure: false, parent, children: FxHashSet::default(), surface, app_id: None, blur: None, viewport, globals, outputs: HashMap::default(), display: None, renderer, bounds: options.bounds, scale: 1.0, input_handler: None, decorations: WindowDecorations::Client, background_appearance: WindowBackgroundAppearance::Opaque, fullscreen: false, maximized: false, tiling: Tiling::default(), window_bounds: options.bounds, in_progress_configure: None, resize_throttle: false, client, appearance, handle, active: false, hovered: false, force_render_after_recovery: false, renderer_presented: false, in_progress_window_controls: None, window_controls: WindowControls::default(), client_inset: None, accesskit_adapter: None, }) } pub fn is_transparent(&self) -> bool { self.decorations == WindowDecorations::Client || self.background_appearance != WindowBackgroundAppearance::Opaque } fn update_subpixel_layout(&mut self) { use wayland_client::protocol::wl_output::Subpixel; let is_bgr = self .display .as_ref() .and_then(|(_, output)| output.subpixel) .is_some_and(|s| s == Subpixel::HorizontalBgr); self.renderer.set_subpixel_layout(is_bgr); } pub fn primary_output_scale(&mut self) -> i32 { let mut scale = 1; let mut current_output = self.display.take(); for (id, output) in self.outputs.iter() { if let Some((_, output_data)) = ¤t_output { if output.scale > output_data.scale { current_output = Some((id.clone(), output.clone())); } } else { current_output = Some((id.clone(), output.clone())); } scale = scale.max(output.scale); } self.display = current_output; scale } pub fn inset(&self) -> Pixels { match self.decorations { WindowDecorations::Server => px(0.0), WindowDecorations::Client => self.client_inset.unwrap_or(px(0.0)), } } } pub(crate) struct WaylandWindow(pub WaylandWindowStatePtr); pub enum ImeInput { InsertText(String), SetMarkedText(String), UnmarkText, DeleteText, } impl Drop for WaylandWindow { fn drop(&mut self) { let mut state = self.0.state.borrow_mut(); let surface_id = state.surface.id(); if let Some(parent) = state.parent.as_ref() { parent.state.borrow_mut().children.remove(&surface_id); } let client = state.client.clone(); state.renderer.destroy(); // Destroy blur first, this has no dependencies. if let Some(blur) = &state.blur { blur.release(); } // Decorations must be destroyed before the xdg state. // See https://wayland.app/protocols/xdg-decoration-unstable-v1#zxdg_toplevel_decoration_v1 if let Some(decoration) = &state.surface_state.decoration() { decoration.destroy(); } // Surface state might contain xdg_toplevel/xdg_surface which can be destroyed now that // decorations are gone. layer_surface has no dependencies. state.surface_state.destroy(); // Viewport must be destroyed before the wl_surface. // See https://wayland.app/protocols/viewporter#wp_viewport if let Some(viewport) = &state.viewport { viewport.destroy(); } // The wl_surface itself should always be destroyed last. state.surface.destroy(); let state_ptr = self.0.clone(); state .globals .executor .spawn(async move { state_ptr.close(); client.drop_window(&surface_id) }) .detach(); drop(state); } } impl WaylandWindow { fn borrow(&self) -> Ref<'_, WaylandWindowState> { self.0.state.borrow() } fn borrow_mut(&self) -> RefMut<'_, WaylandWindowState> { self.0.state.borrow_mut() } pub fn new( handle: AnyWindowHandle, globals: Globals, gpu_context: gpui_wgpu::GpuContext, compositor_gpu: Option, gpu_requirements: Option, client: WaylandClientStatePtr, params: WindowParams, appearance: WindowAppearance, parent: Option, target_output: Option, ) -> anyhow::Result<(Self, ObjectId)> { let surface = globals.compositor.create_surface(&globals.qh, ()); let surface_state = WaylandSurfaceState::new(&surface, &globals, ¶ms, parent.clone(), target_output)?; if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() { fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id()); } let viewport = globals .viewporter .as_ref() .map(|viewporter| viewporter.get_viewport(&surface, &globals.qh, ())); let this = Self(WaylandWindowStatePtr { state: Rc::new(RefCell::new(WaylandWindowState::new( handle, surface.clone(), surface_state, appearance, viewport, client, globals, gpu_context, compositor_gpu, gpu_requirements, params, parent, )?)), callbacks: Rc::new(RefCell::new(Callbacks::default())), }); // Kick things off surface.commit(); Ok((this, surface.id())) } } impl WaylandWindowStatePtr { pub fn handle(&self) -> AnyWindowHandle { self.state.borrow().handle } pub fn surface(&self) -> wl_surface::WlSurface { self.state.borrow().surface.clone() } pub fn toplevel(&self) -> Option { self.state.borrow().surface_state.toplevel().cloned() } pub fn ptr_eq(&self, other: &Self) -> bool { Rc::ptr_eq(&self.state, &other.state) } pub fn add_child(&self, child: ObjectId) { let mut state = self.state.borrow_mut(); state.children.insert(child); } pub fn is_blocked(&self) -> bool { let state = self.state.borrow(); !state.children.is_empty() } pub fn frame(&self) { let mut state = self.state.borrow_mut(); state.surface.frame(&state.globals.qh, state.surface.id()); state.resize_throttle = false; let force_render = state.force_render_after_recovery; state.force_render_after_recovery = false; drop(state); let mut cb = self.callbacks.borrow_mut(); if let Some(fun) = cb.request_frame.as_mut() { fun(RequestFrameOptions { force_render, ..Default::default() }); } } pub fn handle_xdg_surface_event(&self, event: xdg_surface::Event) { if let xdg_surface::Event::Configure { serial } = event { { let mut state = self.state.borrow_mut(); if let Some(window_controls) = state.in_progress_window_controls.take() { state.window_controls = window_controls; drop(state); let mut callbacks = self.callbacks.borrow_mut(); if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() { appearance_changed(); } } } { let mut state = self.state.borrow_mut(); if let Some(mut configure) = state.in_progress_configure.take() { let got_unmaximized = state.maximized && !configure.maximized; state.fullscreen = configure.fullscreen; state.maximized = configure.maximized; state.tiling = configure.tiling; // Limit interactive resizes to once per vblank if configure.resizing && state.resize_throttle { state.surface_state.ack_configure(serial); return; } else if configure.resizing { state.resize_throttle = true; } if !configure.fullscreen && !configure.maximized { configure.size = if got_unmaximized { Some(state.window_bounds.size) } else { compute_outer_size(state.inset(), configure.size, state.tiling) }; if let Some(size) = configure.size { state.window_bounds = Bounds { origin: Point::default(), size, }; } } drop(state); if let Some(size) = configure.size { self.resize(size); } } } let mut state = self.state.borrow_mut(); state.surface_state.ack_configure(serial); let window_geometry = inset_by_tiling( state.bounds.map_origin(|_| px(0.0)), state.inset(), state.tiling, ) .map(|v| f32::from(v) as i32) .map_size(|v| if v <= 0 { 1 } else { v }); state.surface_state.set_geometry( window_geometry.origin.x, window_geometry.origin.y, window_geometry.size.width, window_geometry.size.height, ); let request_frame_callback = !state.acknowledged_first_configure; if request_frame_callback { state.acknowledged_first_configure = true; drop(state); self.frame(); } } } pub fn handle_toplevel_decoration_event(&self, event: zxdg_toplevel_decoration_v1::Event) { if let zxdg_toplevel_decoration_v1::Event::Configure { mode } = event { match mode { WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => { self.state.borrow_mut().decorations = WindowDecorations::Server; let callback = self.callbacks.borrow_mut().appearance_changed.take(); if let Some(mut fun) = callback { fun(); self.callbacks.borrow_mut().appearance_changed = Some(fun); } } WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => { self.state.borrow_mut().decorations = WindowDecorations::Client; // Update background to be transparent let callback = self.callbacks.borrow_mut().appearance_changed.take(); if let Some(mut fun) = callback { fun(); self.callbacks.borrow_mut().appearance_changed = Some(fun); } } WEnum::Value(_) => { log::warn!("Unknown decoration mode"); } WEnum::Unknown(v) => { log::warn!("Unknown decoration mode: {}", v); } } } } pub fn handle_fractional_scale_event(&self, event: wp_fractional_scale_v1::Event) { if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event { self.rescale(scale as f32 / 120.0); } } pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool { match event { xdg_toplevel::Event::Configure { width, height, states, } => { let size = if width == 0 || height == 0 { None } else { Some(size(px(width as f32), px(height as f32))) }; let states = extract_states::(&states); let mut tiling = Tiling::default(); let mut fullscreen = false; let mut maximized = false; let mut resizing = false; for state in states { match state { xdg_toplevel::State::Maximized => { maximized = true; } xdg_toplevel::State::Fullscreen => { fullscreen = true; } xdg_toplevel::State::Resizing => resizing = true, xdg_toplevel::State::TiledTop => { tiling.top = true; } xdg_toplevel::State::TiledLeft => { tiling.left = true; } xdg_toplevel::State::TiledRight => { tiling.right = true; } xdg_toplevel::State::TiledBottom => { tiling.bottom = true; } _ => { // noop } } } if fullscreen || maximized { tiling = Tiling::tiled(); } let mut state = self.state.borrow_mut(); state.in_progress_configure = Some(InProgressConfigure { size, fullscreen, maximized, resizing, tiling, }); false } xdg_toplevel::Event::Close => { let mut cb = self.callbacks.borrow_mut(); if let Some(mut should_close) = cb.should_close.take() { let result = (should_close)(); cb.should_close = Some(should_close); if result { drop(cb); self.close(); } result } else { true } } xdg_toplevel::Event::WmCapabilities { capabilities } => { let mut window_controls = WindowControls { maximize: false, minimize: false, fullscreen: false, window_menu: false, }; let states = extract_states::(&capabilities); for state in states { match state { xdg_toplevel::WmCapabilities::Maximize => { window_controls.maximize = true; } xdg_toplevel::WmCapabilities::Minimize => { window_controls.minimize = true; } xdg_toplevel::WmCapabilities::Fullscreen => { window_controls.fullscreen = true; } xdg_toplevel::WmCapabilities::WindowMenu => { window_controls.window_menu = true; } _ => {} } } let mut state = self.state.borrow_mut(); state.in_progress_window_controls = Some(window_controls); false } _ => false, } } pub fn handle_layersurface_event(&self, event: zwlr_layer_surface_v1::Event) -> bool { match event { zwlr_layer_surface_v1::Event::Configure { width, height, serial, } => { let size = if width == 0 || height == 0 { None } else { Some(size(px(width as f32), px(height as f32))) }; let mut state = self.state.borrow_mut(); state.in_progress_configure = Some(InProgressConfigure { size, fullscreen: false, maximized: false, resizing: false, tiling: Tiling::default(), }); drop(state); // just do the same thing we'd do as an xdg_surface self.handle_xdg_surface_event(xdg_surface::Event::Configure { serial }); false } zwlr_layer_surface_v1::Event::Closed => { // unlike xdg, we don't have a choice here: the surface is closing. true } _ => false, } } #[allow(clippy::mutable_key_type)] pub fn handle_surface_event( &self, event: wl_surface::Event, outputs: HashMap, ) { let mut state = self.state.borrow_mut(); match event { wl_surface::Event::Enter { output } => { let id = output.id(); let Some(output) = outputs.get(&id) else { return; }; state.outputs.insert(id, output.clone()); let scale = state.primary_output_scale(); state.update_subpixel_layout(); // We use `PreferredBufferScale` instead to set the scale if it's available if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE { state.surface.set_buffer_scale(scale); drop(state); self.rescale(scale as f32); } } wl_surface::Event::Leave { output } => { state.outputs.remove(&output.id()); let scale = state.primary_output_scale(); state.update_subpixel_layout(); // We use `PreferredBufferScale` instead to set the scale if it's available if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE { state.surface.set_buffer_scale(scale); drop(state); self.rescale(scale as f32); } } wl_surface::Event::PreferredBufferScale { factor } => { // We use `WpFractionalScale` instead to set the scale if it's available if state.globals.fractional_scale_manager.is_none() { state.surface.set_buffer_scale(factor); drop(state); self.rescale(factor as f32); } } _ => {} } } pub fn handle_ime(&self, ime: ImeInput) { if self.is_blocked() { return; } let mut state = self.state.borrow_mut(); if let Some(mut input_handler) = state.input_handler.take() { drop(state); match ime { ImeInput::InsertText(text) => { input_handler.replace_text_in_range(None, &text); } ImeInput::SetMarkedText(text) => { input_handler.replace_and_mark_text_in_range(None, &text, None); } ImeInput::UnmarkText => { input_handler.unmark_text(); } ImeInput::DeleteText => { if let Some(marked) = input_handler.marked_text_range() { input_handler.replace_text_in_range(Some(marked), ""); } } } self.state.borrow_mut().input_handler = Some(input_handler); } } pub fn get_ime_area(&self) -> Option> { let mut state = self.state.borrow_mut(); let mut bounds: Option> = None; if let Some(mut input_handler) = state.input_handler.take() { drop(state); if let Some(selection) = input_handler.marked_text_range() { bounds = input_handler.bounds_for_range(selection.start..selection.start); } self.state.borrow_mut().input_handler = Some(input_handler); } bounds } pub fn set_size_and_scale(&self, size: Option>, scale: Option) { let (size, scale) = { let mut state = self.state.borrow_mut(); if size.is_none_or(|size| size == state.bounds.size) && scale.is_none_or(|scale| scale == state.scale) { return; } if let Some(size) = size { state.bounds.size = size; } if let Some(scale) = scale { state.scale = scale; } let device_bounds = state.bounds.to_device_pixels(state.scale); state.renderer.update_drawable_size(device_bounds.size); (state.bounds.size, state.scale) }; let callback = self.callbacks.borrow_mut().resize.take(); if let Some(mut fun) = callback { fun(size, scale); self.callbacks.borrow_mut().resize = Some(fun); } { let state = self.state.borrow(); if let Some(viewport) = &state.viewport { viewport .set_destination(f32::from(size.width) as i32, f32::from(size.height) as i32); } } } pub fn resize(&self, size: Size) { self.set_size_and_scale(Some(size), None); } pub fn rescale(&self, scale: f32) { self.set_size_and_scale(None, Some(scale)); } pub fn close(&self) { let state = self.state.borrow(); let client = state.client.get_client(); #[allow(clippy::mutable_key_type)] let children = state.children.clone(); drop(state); for child in children { let mut client_state = client.borrow_mut(); let window = get_window(&mut client_state, &child); drop(client_state); if let Some(child) = window { child.close(); } } let mut callbacks = self.callbacks.borrow_mut(); if let Some(fun) = callbacks.close.take() { fun() } } pub fn handle_input(&self, input: PlatformInput) { if self.is_blocked() { return; } let callback = self.callbacks.borrow_mut().input.take(); if let Some(mut fun) = callback { let result = fun(input.clone()); self.callbacks.borrow_mut().input = Some(fun); if !result.propagate { return; } } if let PlatformInput::KeyDown(event) = input && event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) && let Some(key_char) = &event.keystroke.key_char { let mut state = self.state.borrow_mut(); if let Some(mut input_handler) = state.input_handler.take() { drop(state); input_handler.replace_text_in_range(None, key_char); self.state.borrow_mut().input_handler = Some(input_handler); } } } pub fn set_focused(&self, focus: bool) { self.state.borrow_mut().active = focus; let callback = self.callbacks.borrow_mut().active_status_change.take(); if let Some(mut fun) = callback { fun(focus); self.callbacks.borrow_mut().active_status_change = Some(fun); } if let Some(adapter) = self.state.borrow_mut().accesskit_adapter.as_mut() { adapter.update_window_focus_state(focus); } } pub fn set_hovered(&self, focus: bool) { let callback = self.callbacks.borrow_mut().hover_status_change.take(); if let Some(mut fun) = callback { fun(focus); self.callbacks.borrow_mut().hover_status_change = Some(fun); } } pub fn set_appearance(&mut self, appearance: WindowAppearance) { self.state.borrow_mut().appearance = appearance; let callback = self.callbacks.borrow_mut().appearance_changed.take(); if let Some(mut fun) = callback { fun(); self.callbacks.borrow_mut().appearance_changed = Some(fun); } } pub fn set_button_layout(&self) { let callback = self.callbacks.borrow_mut().button_layout_changed.take(); if let Some(mut fun) = callback { fun(); self.callbacks.borrow_mut().button_layout_changed = Some(fun); } } pub fn primary_output_scale(&self) -> i32 { self.state.borrow_mut().primary_output_scale() } } fn extract_states<'a, S: TryFrom + 'a>(states: &'a [u8]) -> impl Iterator + 'a where >::Error: 'a, { states .chunks_exact(4) .flat_map(TryInto::<[u8; 4]>::try_into) .map(u32::from_ne_bytes) .flat_map(S::try_from) } impl rwh::HasWindowHandle for WaylandWindow { fn window_handle(&self) -> Result, rwh::HandleError> { let surface = self.0.surface().id().as_ptr() as *mut libc::c_void; let c_ptr = NonNull::new(surface).ok_or(rwh::HandleError::Unavailable)?; let handle = rwh::WaylandWindowHandle::new(c_ptr); let raw_handle = rwh::RawWindowHandle::Wayland(handle); Ok(unsafe { rwh::WindowHandle::borrow_raw(raw_handle) }) } } impl rwh::HasDisplayHandle for WaylandWindow { fn display_handle(&self) -> Result, rwh::HandleError> { let display = self .0 .surface() .backend() .upgrade() .ok_or(rwh::HandleError::Unavailable)? .display_ptr() as *mut libc::c_void; let c_ptr = NonNull::new(display).ok_or(rwh::HandleError::Unavailable)?; let handle = rwh::WaylandDisplayHandle::new(c_ptr); let raw_handle = rwh::RawDisplayHandle::Wayland(handle); Ok(unsafe { rwh::DisplayHandle::borrow_raw(raw_handle) }) } } impl PlatformWindow for WaylandWindow { fn bounds(&self) -> Bounds { self.borrow().bounds } fn is_maximized(&self) -> bool { self.borrow().maximized } fn window_bounds(&self) -> WindowBounds { let state = self.borrow(); if state.fullscreen { WindowBounds::Fullscreen(state.window_bounds) } else if state.maximized { WindowBounds::Maximized(state.window_bounds) } else { drop(state); WindowBounds::Windowed(self.bounds()) } } fn inner_window_bounds(&self) -> WindowBounds { let state = self.borrow(); if state.fullscreen { WindowBounds::Fullscreen(state.window_bounds) } else if state.maximized { WindowBounds::Maximized(state.window_bounds) } else { let inset = state.inset(); drop(state); WindowBounds::Windowed(self.bounds().inset(inset)) } } fn content_size(&self) -> Size { self.borrow().bounds.size } fn resize(&mut self, size: Size) { let state = self.borrow(); let state_ptr = self.0.clone(); // Keep window geometry consistent with configure handling. On Wayland, window geometry is // surface-local: resizing should not attempt to translate the window; the compositor // controls placement. We also account for client-side decoration insets and tiling. let window_geometry = inset_by_tiling( Bounds { origin: Point::default(), size, }, state.inset(), state.tiling, ) .map(|v| f32::from(v) as i32) .map_size(|v| if v <= 0 { 1 } else { v }); state.surface_state.set_geometry( window_geometry.origin.x, window_geometry.origin.y, window_geometry.size.width, window_geometry.size.height, ); state .globals .executor .spawn(async move { state_ptr.resize(size) }) .detach(); } fn scale_factor(&self) -> f32 { self.borrow().scale } fn appearance(&self) -> WindowAppearance { self.borrow().appearance } fn display(&self) -> Option> { let state = self.borrow(); state.display.as_ref().map(|(id, display)| { Rc::new(WaylandDisplay { id: id.clone(), name: display.name.clone(), bounds: display.bounds.to_pixels(state.scale), }) as Rc }) } fn mouse_position(&self) -> Point { self.borrow() .client .get_client() .borrow() .mouse_location .unwrap_or_default() } fn modifiers(&self) -> Modifiers { self.borrow().client.get_client().borrow().modifiers } fn capslock(&self) -> Capslock { self.borrow().client.get_client().borrow().capslock } fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { self.borrow_mut().input_handler = Some(input_handler); } fn take_input_handler(&mut self) -> Option { self.borrow_mut().input_handler.take() } fn prompt( &self, _level: PromptLevel, _msg: &str, _detail: Option<&str>, _answers: &[PromptButton], ) -> Option> { None } fn activate(&self) { // Try to request an activation token. Even though the activation is likely going to be rejected, // KWin and Mutter can use the app_id to visually indicate we're requesting attention. let state = self.borrow(); if let (Some(activation), Some(app_id)) = (&state.globals.activation, state.app_id.clone()) { state.client.set_pending_activation(state.surface.id()); let token = activation.get_activation_token(&state.globals.qh, ()); // The serial isn't exactly important here, since the activation is probably going to be rejected anyway. let serial = state.client.get_serial(SerialKind::MousePress); token.set_app_id(app_id); token.set_serial(serial, &state.globals.seat); token.set_surface(&state.surface); token.commit(); } } fn is_active(&self) -> bool { self.borrow().active } fn is_hovered(&self) -> bool { self.borrow().hovered } fn set_title(&mut self, title: &str) { if let Some(toplevel) = self.borrow().surface_state.toplevel() { toplevel.set_title(title.to_string()); } } fn set_app_id(&mut self, app_id: &str) { let mut state = self.borrow_mut(); if let Some(toplevel) = state.surface_state.toplevel() { toplevel.set_app_id(app_id.to_owned()); } state.app_id = Some(app_id.to_owned()); } fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) { let mut state = self.borrow_mut(); state.background_appearance = background_appearance; update_window(state); } fn background_appearance(&self) -> WindowBackgroundAppearance { self.borrow().background_appearance } fn is_subpixel_rendering_supported(&self) -> bool { let client = self.borrow().client.get_client(); let state = client.borrow(); state .gpu_context .borrow() .as_ref() .is_some_and(|ctx| ctx.supports_dual_source_blending()) } fn minimize(&self) { if let Some(toplevel) = self.borrow().surface_state.toplevel() { toplevel.set_minimized(); } } fn zoom(&self) { let state = self.borrow(); if let Some(toplevel) = state.surface_state.toplevel() { if !state.maximized { toplevel.set_maximized(); } else { toplevel.unset_maximized(); } } } fn toggle_fullscreen(&self) { let state = self.borrow(); if let Some(toplevel) = state.surface_state.toplevel() { if !state.fullscreen { toplevel.set_fullscreen(None); } else { toplevel.unset_fullscreen(); } } } fn is_fullscreen(&self) -> bool { self.borrow().fullscreen } fn on_request_frame(&self, callback: Box) { self.0.callbacks.borrow_mut().request_frame = Some(callback); } fn on_input(&self, callback: Box gpui::DispatchEventResult>) { self.0.callbacks.borrow_mut().input = Some(callback); } fn on_active_status_change(&self, callback: Box) { self.0.callbacks.borrow_mut().active_status_change = Some(callback); } fn on_hover_status_change(&self, callback: Box) { self.0.callbacks.borrow_mut().hover_status_change = Some(callback); } fn on_resize(&self, callback: Box, f32)>) { self.0.callbacks.borrow_mut().resize = Some(callback); } fn on_moved(&self, callback: Box) { self.0.callbacks.borrow_mut().moved = Some(callback); } fn on_should_close(&self, callback: Box bool>) { self.0.callbacks.borrow_mut().should_close = Some(callback); } fn on_close(&self, callback: Box) { self.0.callbacks.borrow_mut().close = Some(callback); } fn on_hit_test_window_control(&self, _callback: Box Option>) { } fn on_appearance_changed(&self, callback: Box) { self.0.callbacks.borrow_mut().appearance_changed = Some(callback); } fn on_button_layout_changed(&self, callback: Box) { self.0.callbacks.borrow_mut().button_layout_changed = Some(callback); } fn draw(&self, scene: &Scene) { let mut state = self.borrow_mut(); if state.renderer.device_lost() { let raw_window = RawWindow { window: state.surface.id().as_ptr().cast::(), display: state .surface .backend() .upgrade() .unwrap() .display_ptr() .cast::(), }; match state.renderer.recover(&raw_window) { Ok(()) => {} Err(err) => { log::warn!("GPU recovery failed, will retry on next frame: {err}"); } } state.force_render_after_recovery = true; return; } state.renderer_presented = state.renderer.draw(scene); if state.renderer.needs_redraw() { state.force_render_after_recovery = true; } } fn completed_frame(&self) { let mut state = self.borrow_mut(); // Work around a bug in old versions of wlroots where committing without a buffer attached // can cause invalid synchronization that leads to graphical corruption. if !state.renderer_presented { state.surface.commit(); } state.renderer_presented = false; } fn sprite_atlas(&self) -> Arc { let state = self.borrow(); state.renderer.sprite_atlas().clone() } fn show_window_menu(&self, position: Point) { let state = self.borrow(); let serial = state.client.get_serial(SerialKind::MousePress); if let Some(toplevel) = state.surface_state.toplevel() { toplevel.show_window_menu( &state.globals.seat, serial, f32::from(position.x) as i32, f32::from(position.y) as i32, ); } } fn start_window_move(&self) { let state = self.borrow(); let serial = state.client.get_serial(SerialKind::MousePress); if let Some(toplevel) = state.surface_state.toplevel() { toplevel._move(&state.globals.seat, serial); } } fn start_window_resize(&self, edge: gpui::ResizeEdge) { let state = self.borrow(); if let Some(toplevel) = state.surface_state.toplevel() { toplevel.resize( &state.globals.seat, state.client.get_serial(SerialKind::MousePress), edge.to_xdg(), ) } } fn set_input_region(&self, rects: &[Bounds]) { let state = self.borrow(); if rects.is_empty() { state.surface.set_input_region(None); } else { let region = state .globals .compositor .create_region(&state.globals.qh, ()); rects .iter() .map(|rect| rect.map(|pixels| f32::from(pixels) as i32)) .for_each(|rect| { region.add( rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, ) }); state.surface.set_input_region(Some(®ion)); region.destroy(); } state.surface.commit(); } fn set_exclusive_zone(&self, zone: Pixels) { let state = self.borrow(); state .surface_state .set_exclusive_zone(f32::from(zone) as i32); state.surface.commit(); } fn window_decorations(&self) -> Decorations { let state = self.borrow(); match state.decorations { WindowDecorations::Server => Decorations::Server, WindowDecorations::Client => Decorations::Client { tiling: state.tiling, }, } } fn request_decorations(&self, decorations: WindowDecorations) { let mut state = self.borrow_mut(); match state.surface_state.decoration().as_ref() { Some(decoration) => { decoration.set_mode(decorations.to_xdg()); state.decorations = decorations; update_window(state); } None => { if matches!(decorations, WindowDecorations::Server) { log::info!( "Server-side decorations requested, but the Wayland server does not support them. Falling back to client-side decorations." ); } state.decorations = WindowDecorations::Client; update_window(state); } } } fn window_controls(&self) -> WindowControls { self.borrow().window_controls } fn set_client_inset(&self, inset: Pixels) { let mut state = self.borrow_mut(); if Some(inset) != state.client_inset { state.client_inset = Some(inset); update_window(state); } } fn update_ime_position(&self, bounds: Bounds) { let state = self.borrow(); state.client.update_ime_position(bounds); } fn gpu_specs(&self) -> Option { self.borrow().renderer.gpu_specs().into() } fn gpu_context(&self) -> Option> { let (device, queue) = self.borrow().renderer.gpu_context(); Some(Box::new((device, queue))) } fn gpu_device_lost(&self) -> Option { // Only loads an atomic flag — safe even mid-recovery, when // `gpu_context` would panic on the torn-down resources. Some(self.borrow().renderer.device_lost()) } fn play_system_bell(&self) { let state = self.borrow(); let surface = if state.surface_state.toplevel().is_some() { Some(&state.surface) } else { None }; if let Some(bell) = state.globals.system_bell.as_ref() { bell.ring(surface); } } fn a11y_init(&self, callbacks: gpui::A11yCallbacks) { let activation_handler = TrivialActivationHandler { callback: callbacks.activation, }; let action_handler = TrivialActionHandler(callbacks.action); let deactivation_handler = TrivialDeactivationHandler { callback: callbacks.deactivation, }; let adapter = accesskit_unix::Adapter::new(activation_handler, action_handler, deactivation_handler); self.borrow_mut().accesskit_adapter = Some(adapter); } fn a11y_tree_update(&self, tree_update: accesskit::TreeUpdate) { let mut state = self.borrow_mut(); if let Some(adapter) = state.accesskit_adapter.as_mut() { adapter.update_if_active(|| tree_update); } } fn a11y_update_window_bounds(&self) { // Wayland doesn't expose window position, so this is a no-op } } struct TrivialActivationHandler { callback: Box Option + Send + 'static>, } impl accesskit::ActivationHandler for TrivialActivationHandler { fn request_initial_tree(&mut self) -> Option { (self.callback)() } } struct TrivialActionHandler(Box); impl accesskit::ActionHandler for TrivialActionHandler { fn do_action(&mut self, request: accesskit::ActionRequest) { (self.0)(request); } } struct TrivialDeactivationHandler { callback: Box, } impl accesskit::DeactivationHandler for TrivialDeactivationHandler { fn deactivate_accessibility(&mut self) { (self.callback)(); } } fn update_window(mut state: RefMut) { let opaque = !state.is_transparent(); state.renderer.update_transparency(!opaque); let opaque_area = state.window_bounds.map(|v| f32::from(v) as i32); opaque_area.inset(f32::from(state.inset()) as i32); let region = state .globals .compositor .create_region(&state.globals.qh, ()); region.add( opaque_area.origin.x, opaque_area.origin.y, opaque_area.size.width, opaque_area.size.height, ); // Note that rounded corners make this rectangle API hard to work with. // As this is common when using CSD, let's just disable this API. if state.background_appearance == WindowBackgroundAppearance::Opaque && state.decorations == WindowDecorations::Server { // Promise the compositor that this region of the window surface // contains no transparent pixels. This allows the compositor to skip // updating whatever is behind the surface for better performance. state.surface.set_opaque_region(Some(®ion)); } else { state.surface.set_opaque_region(None); } if let Some(ref blur_manager) = state.globals.blur_manager { if state.background_appearance == WindowBackgroundAppearance::Blurred { if state.blur.is_none() { let blur = blur_manager.create(&state.surface, &state.globals.qh, ()); state.blur = Some(blur); } state.blur.as_ref().unwrap().commit(); } else { // It probably doesn't hurt to clear the blur for opaque windows blur_manager.unset(&state.surface); if let Some(b) = state.blur.take() { b.release() } } } region.destroy(); } pub(crate) trait WindowDecorationsExt { fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode; } impl WindowDecorationsExt for WindowDecorations { fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode { match self { WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide, WindowDecorations::Server => zxdg_toplevel_decoration_v1::Mode::ServerSide, } } } pub(crate) trait ResizeEdgeWaylandExt { fn to_xdg(self) -> xdg_toplevel::ResizeEdge; } impl ResizeEdgeWaylandExt for ResizeEdge { fn to_xdg(self) -> xdg_toplevel::ResizeEdge { match self { ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top, ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight, ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right, ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight, ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom, ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft, ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left, ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft, } } } /// The configuration event is in terms of the window geometry, which we are constantly /// updating to account for the client decorations. But that's not the area we want to render /// to, due to our intrusize CSD. So, here we calculate the 'actual' size, by adding back in the insets fn compute_outer_size( inset: Pixels, new_size: Option>, tiling: Tiling, ) -> Option> { new_size.map(|mut new_size| { if !tiling.top { new_size.height += inset; } if !tiling.bottom { new_size.height += inset; } if !tiling.left { new_size.width += inset; } if !tiling.right { new_size.width += inset; } new_size }) } fn inset_by_tiling(mut bounds: Bounds, inset: Pixels, tiling: Tiling) -> Bounds { if !tiling.top { bounds.origin.y += inset; bounds.size.height -= inset; } if !tiling.bottom { bounds.size.height -= inset; } if !tiling.left { bounds.origin.x += inset; bounds.size.width -= inset; } if !tiling.right { bounds.size.width -= inset; } bounds }