diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 6f564ba900..82967013d7 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -46,6 +46,13 @@ impl fmt::Debug for WindowId { } } +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub enum WindowType { + #[default] + Window, + Popup, +} + /// Attributes used when creating a window. #[derive(Debug)] #[non_exhaustive] @@ -72,6 +79,7 @@ pub struct WindowAttributes { pub(crate) parent_window: Option, pub fullscreen: Option, pub platform: Option>, + pub window_type: WindowType, } impl WindowAttributes { @@ -145,6 +153,7 @@ impl WindowAttributes { /// position. There may be a small gap between this position and the window due to the /// specifics of the Window Manager. /// - **X11:** The top left corner of the window, the window's "outer" position. + /// - **Wayland:** The top left corner of the window if the window type is `WindowType::Popup` otherwise ignored /// - **Others:** Ignored. #[inline] pub fn with_position>(mut self, position: P) -> Self { @@ -378,6 +387,22 @@ impl WindowAttributes { self.platform = Some(platform); self } + + /// Sets the window type of the object + /// + /// Currently only wayland is using this type. On X11 popups are also just normal windows + /// Note: If the type is set to `WindowType::Popup` the parent must be set as well with + /// `with_parent_window()`. + pub fn as_type(mut self, window_type: WindowType) -> Self { + self.window_type = window_type; + self + } + + /// Returns if the window type is a popup or a normal window + #[inline] + pub fn popup(&self) -> bool { + self.window_type == WindowType::Popup + } } impl Clone for WindowAttributes { @@ -405,6 +430,7 @@ impl Clone for WindowAttributes { parent_window: self.parent_window.clone(), fullscreen: self.fullscreen.clone(), platform: self.platform.as_ref().map(|platform| platform.box_clone()), + window_type: self.window_type, } } } @@ -435,6 +461,7 @@ impl Default for WindowAttributes { platform: Default::default(), cursor: Cursor::default(), blur: Default::default(), + window_type: Default::default(), } } } diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 3a6ca6982b..a728fa47fb 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -651,8 +651,13 @@ impl RootActiveEventLoop for ActiveEventLoop { &self, window_attributes: winit_core::window::WindowAttributes, ) -> Result, RequestError> { - let window = crate::Window::new(self, window_attributes)?; - Ok(Box::new(window)) + if window_attributes.popup() { + let popup = crate::Popup::new(self, window_attributes)?; + Ok(Box::new(popup)) + } else { + let window = crate::Window::new(self, window_attributes)?; + Ok(Box::new(window)) + } } fn available_monitors(&self) -> Box> { diff --git a/winit-wayland/src/lib.rs b/winit-wayland/src/lib.rs index 930562b380..594dd0ff1d 100644 --- a/winit-wayland/src/lib.rs +++ b/winit-wayland/src/lib.rs @@ -40,9 +40,11 @@ mod seat; mod state; mod types; mod window; +mod popup; pub use self::event_loop::{ActiveEventLoop, EventLoop}; pub use self::window::Window; +pub use self::popup::Popup; /// Additional methods on [`ActiveEventLoop`] that are specific to Wayland. pub trait ActiveEventLoopExtWayland { diff --git a/winit-wayland/src/popup.rs b/winit-wayland/src/popup.rs new file mode 100644 index 0000000000..79b482dc25 --- /dev/null +++ b/winit-wayland/src/popup.rs @@ -0,0 +1,550 @@ +use super::ActiveEventLoop; +use crate::window::WindowRequests; +use crate::window::state::{WindowState, WindowType}; +use core::sync::atomic::Ordering; +use dpi::{LogicalPosition, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; +use rwh_06::RawWindowHandle; +use sctk::shell::WaylandSurface; +use sctk::shell::xdg::popup::Popup as SctkPopup; +use sctk::shell::xdg::{XdgPositioner, XdgSurface}; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex}; +use wayland_client::Proxy; +use wayland_client::protocol::wl_display::WlDisplay; +use wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; +use winit_core::cursor::Cursor; +use winit_core::error::{NotSupportedError, RequestError}; +use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; +use winit_core::window::{ + CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme, + UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, + WindowLevel, +}; + +#[derive(Debug)] +pub struct Popup { + /// The state of the popup. + popup_state: Arc>, + + /// Window id. + window_id: WindowId, + + /// The wayland display used solely for raw window handle. + #[allow(dead_code)] + display: WlDisplay, + + /// Window requests to the event loop. + /// Used for example to close the popup + window_requests: Arc, + + /// Source to wake-up the event-loop for window requests. + event_loop_awakener: calloop::ping::Ping, +} + +impl Popup { + pub(crate) fn new( + event_loop_window_target: &ActiveEventLoop, + attributes: WindowAttributes, + ) -> Result { + macro_rules! error { + ($e:literal) => { + RequestError::NotSupported(NotSupportedError::new($e)) + }; + } + + let parent_window_handle = + attributes.parent_window().ok_or(error!("Popup without a parent is not supported!"))?; + if let RawWindowHandle::Wayland(parent_window_handle) = parent_window_handle { + let queue_handle = event_loop_window_target.queue_handle.clone(); + let mut state = event_loop_window_target.state.borrow_mut(); + let positioner = XdgPositioner::new(&state.xdg_shell) + .map_err(|_| error!("Failed to create positioner"))?; + let (popup, popup_state) = if let Some(parent_window_state) = state + .windows + .borrow() + .get(&WindowId::from_raw(parent_window_handle.surface.as_ptr() as usize)) + { + let size = attributes.surface_size.ok_or(error!("Invalid size for popup"))?; + + let parent_window_state = parent_window_state.lock().unwrap(); + + // Use the scale factor and xdg geometry of the parent. + let scale_factor = parent_window_state.scale_factor(); + let position: LogicalPosition = attributes + .position + .ok_or(error!("No position specified"))? + .to_logical(scale_factor); + let geometry_origin = parent_window_state.content_surface_origin(); + // The anchor rect is relative to the parent window geometry, so we need to subtract + // the geometry origin from the position to get the correct anchor rect. + let anchor_position = LogicalPosition::new( + position.x - geometry_origin.x, + position.y - geometry_origin.y, + ); + + positioner.set_anchor(Anchor::TopLeft); + positioner.set_gravity(Gravity::BottomRight); // Otherwise the child surface will be centered over the anchor point + positioner.set_anchor_rect(anchor_position.x, anchor_position.y, 1, 1); + positioner.set_offset(0, 0); + positioner.set_size( + size.to_logical(scale_factor).width, + size.to_logical(scale_factor).height, + ); + + let parent_surface = parent_window_state.window.xdg_surface(); + let surface = state.compositor_state.create_surface(&queue_handle); + let popup = SctkPopup::from_surface( + Some(parent_surface), + &positioner, + &queue_handle, + surface.clone(), + &state.xdg_shell, + ) + .map_err(|_| error!("Failed to create popup"))?; + drop(parent_window_state); + + let popup_state = WindowState::new( + event_loop_window_target.handle.clone(), + &event_loop_window_target.queue_handle, + &state, + size, + WindowType::Popup((popup.clone(), positioner, None)), + attributes.preferred_theme, + false, + scale_factor, + ); + + popup.wl_surface().commit(); + // popup.commit(); Trait not implemented in Sctk + + let popup_state = Arc::new(Mutex::new(popup_state)); + + (popup, popup_state) + } else { + return Err(error!("Parent window id unknown")); + }; + + let window_id = super::make_wid(&popup.wl_surface()); + state.windows.get_mut().insert(window_id, popup_state.clone()); + + let window_requests = WindowRequests { + redraw_requested: AtomicBool::new(true), + closed: AtomicBool::new(false), + }; + let window_requests = Arc::new(window_requests); + state.window_requests.get_mut().insert(window_id, window_requests.clone()); + + let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); + let event_queue = wayland_source.queue(); + // Do a roundtrip. + event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?; + + // XXX Wait for the initial configure to arrive. + while !popup_state.lock().unwrap().is_configured() { + event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?; + } + + let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); + + Ok(Self { + popup_state, + window_id, + display: event_loop_window_target.handle.connection.display().clone(), + event_loop_awakener, + window_requests, + }) + } else { + Err(RequestError::NotSupported(NotSupportedError::new( + "Not a wayland window handle passed", + ))) + } + } +} + +impl CoreWindow for Popup { + fn id(&self) -> WindowId { + self.window_id + } + + fn request_redraw(&self) { + // // NOTE: try to not wake up the loop when the event was already scheduled and not yet + // // processed by the loop, because if at this point the value was `true` it could only + // // mean that the loop still haven't dispatched the value to the client and will do + // // eventually, resetting it to `false`. + // if self + // .window_requests + // .redraw_requested + // .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + // .is_ok() + // { + // self.event_loop_awakener.ping(); + // } + } + + #[inline] + fn title(&self) -> String { + self.popup_state.lock().unwrap().title().to_owned() + } + + fn pre_present_notify(&self) { + // self.popup_state.lock().unwrap().request_frame_callback(); + } + + fn reset_dead_keys(&self) { + winit_common::xkb::reset_dead_keys() + } + + fn surface_position(&self) -> PhysicalPosition { + (0, 0).into() + } + + fn outer_position(&self) -> Result, RequestError> { + Err(NotSupportedError::new("window position information is not available on Wayland") + .into()) + } + + fn set_outer_position(&self, _position: Position) { + // Not possible. + } + + fn surface_size(&self) -> PhysicalSize { + let popup_state = self.popup_state.lock().unwrap(); + let scale_factor = popup_state.scale_factor(); + super::logical_to_physical_rounded(popup_state.surface_size(), scale_factor) + } + + fn request_surface_size(&self, size: Size) -> Option> { + let mut popup_state = self.popup_state.lock().unwrap(); + popup_state.request_surface_size(size); + self.request_redraw(); + Some(size.to_physical(popup_state.scale_factor())) + } + + fn outer_size(&self) -> PhysicalSize { + // let popup_state = self.popup_state.lock().unwrap(); + // let scale_factor = popup_state.scale_factor(); + // super::logical_to_physical_rounded(popup_state.outer_size(), scale_factor) + PhysicalSize::new(100, 100) + } + + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) + } + + fn set_min_surface_size(&self, min_size: Option) { + // let scale_factor = self.scale_factor(); + // let min_size = min_size.map(|size| size.to_logical(scale_factor)); + // self.state.lock().unwrap().set_min_surface_size(min_size); + // // NOTE: Requires commit to be applied. + // self.request_redraw(); + } + + /// Set the maximum surface size for the window. + #[inline] + fn set_max_surface_size(&self, max_size: Option) { + // let scale_factor = self.scale_factor(); + // let max_size = max_size.map(|size| size.to_logical(scale_factor)); + // self.popup_state.lock().unwrap().set_max_surface_size(max_size); + // // NOTE: Requires commit to be applied. + // self.request_redraw(); + } + + fn surface_resize_increments(&self) -> Option> { + // let popup_state = self.popup_state.lock().unwrap(); + // let scale_factor = popup_state.scale_factor(); + // popup_state + // .resize_increments() + // .map(|size| super::logical_to_physical_rounded(size, scale_factor)) + None + } + + fn set_surface_resize_increments(&self, increments: Option) { + // let mut popup_state = self.popup_state.lock().unwrap(); + // let scale_factor = popup_state.scale_factor(); + // let increments = increments.map(|size| size.to_logical(scale_factor)); + // popup_state.set_resize_increments(increments); + } + + fn set_title(&self, title: &str) { + self.popup_state.lock().unwrap().set_title(title.to_owned()); + } + + #[inline] + fn set_transparent(&self, transparent: bool) { + self.popup_state.lock().unwrap().set_transparent(transparent); + } + + fn set_visible(&self, _visible: bool) { + // Not possible on Wayland. + } + + fn is_visible(&self) -> Option { + None + } + + fn set_resizable(&self, resizable: bool) { + // if self.popup_state.lock().unwrap().set_resizable(resizable) { + // // NOTE: Requires commit to be applied. + // self.request_redraw(); + // } + } + + fn is_resizable(&self) -> bool { + // TODO + // self.popup_state.lock().unwrap().resizable() + false + } + + fn set_enabled_buttons(&self, _buttons: WindowButtons) { + // TODO(kchibisov) v5 of the xdg_shell allows that. + } + + fn enabled_buttons(&self) -> WindowButtons { + // TODO(kchibisov) v5 of the xdg_shell allows that. + WindowButtons::all() + } + + fn set_minimized(&self, _minimized: bool) { + // Not possible for popups + } + + fn is_minimized(&self) -> Option { + // XXX clients don't know whether they are minimized or not. + None + } + + fn set_maximized(&self, _maximized: bool) { + // Not possible for popups + } + + fn is_maximized(&self) -> bool { + // Not possible for popups + false + } + + fn set_fullscreen(&self, fullscreen: Option) { + // Not possible for popups + } + + fn fullscreen(&self) -> Option { + None + } + + #[inline] + fn scale_factor(&self) -> f64 { + self.popup_state.lock().unwrap().scale_factor() + } + + #[inline] + fn set_blur(&self, blur: bool) { + // self.popup_state.lock().unwrap().set_blur(blur); + } + + #[inline] + fn set_decorations(&self, decorate: bool) { + // self.popup_state.lock().unwrap().set_decorate(decorate) + } + + #[inline] + fn is_decorated(&self) -> bool { + // self.popup_state.lock().unwrap().is_decorated() + false + } + + fn set_window_level(&self, _level: WindowLevel) {} + + fn set_window_icon(&self, window_icon: Option) { + // self.popup_state.lock().unwrap().set_window_icon(window_icon) + } + + #[inline] + fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> { + // let state_changed = self.popup_state.lock().unwrap().request_ime_update(request)?; + + // if let Some(allowed) = state_changed { + // let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); + // self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id); + // self.event_loop_awakener.ping(); + // } + + Ok(()) + } + + #[inline] + fn ime_capabilities(&self) -> Option { + // self.popup_state.lock().unwrap().ime_allowed() + None + } + + fn focus_window(&self) {} + + fn has_focus(&self) -> bool { + // self.popup_state.lock().unwrap().has_focus() + false + } + + fn request_user_attention(&self, request_type: Option) { + // let xdg_activation = match self.xdg_activation.as_ref() { + // Some(xdg_activation) => xdg_activation, + // None => { + // warn!("`request_user_attention` isn't supported"); + // return; + // }, + // }; + + // // Urgency is only removed by the compositor and there's no need to raise urgency when it + // // was already raised. + // if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { + // return; + // } + + // self.attention_requested.store(true, Ordering::Relaxed); + // let surface = self.surface().clone(); + // let data = XdgActivationTokenData::Attention(( + // surface.clone(), + // Arc::downgrade(&self.attention_requested), + // )); + // let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); + // xdg_activation_token.set_surface(&surface); + // xdg_activation_token.commit(); + } + + fn set_theme(&self, theme: Option) { + // self.popup_state.lock().unwrap().set_theme(theme) + } + + fn theme(&self) -> Option { + // self.popup_state.lock().unwrap().theme() + None + } + + fn set_content_protected(&self, _protected: bool) {} + + fn set_cursor(&self, cursor: Cursor) { + // let popup_state = &mut self.popup_state.lock().unwrap(); + + // match cursor { + // Cursor::Icon(icon) => popup_state.set_cursor(icon), + // Cursor::Custom(cursor) => popup_state.set_custom_cursor(cursor), + // } + } + + fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> { + // let scale_factor = self.scale_factor(); + // let position = position.to_logical(scale_factor); + // self.popup_state + // .lock() + // .unwrap() + // .set_cursor_position(position) + // // Request redraw on success, since the state is double buffered. + // .map(|_| self.request_redraw()) + Err(RequestError::Ignored) + } + + fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> { + // self.popup_state.lock().unwrap().set_cursor_grab(mode) + Err(RequestError::Ignored) + } + + fn set_cursor_visible(&self, visible: bool) { + // self.popup_state.lock().unwrap().set_cursor_visible(visible); + } + + fn drag_window(&self) -> Result<(), RequestError> { + // Popup does not support dragging + Err(RequestError::Ignored) + } + + fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> { + // TODO: implement + // self.popup_state.lock().unwrap().drag_resize_window(direction) + Err(RequestError::Ignored) + } + + fn show_window_menu(&self, position: Position) { + // let scale_factor = self.scale_factor(); + // let position = position.to_logical(scale_factor); + // self.popup_state.lock().unwrap().show_window_menu(position); + } + + fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { + // let surface = self.window.wl_surface(); + + // if hittest { + // surface.set_input_region(None); + // Ok(()) + // } else { + // let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?; + // region.add(0, 0, 0, 0); + // surface.set_input_region(Some(region.wl_region())); + // Ok(()) + // } + Err(RequestError::Ignored) + } + + fn current_monitor(&self) -> Option { + // let data = self.window.wl_surface().data::()?; + // data.outputs() + // .next() + // .map(MonitorHandle::new) + // .map(|monitor| CoreMonitorHandle(Arc::new(monitor))) + None + } + + fn available_monitors(&self) -> Box> { + // Box::new( + // self.monitors + // .lock() + // .unwrap() + // .clone() + // .into_iter() + // .map(|inner| CoreMonitorHandle(Arc::new(inner))), + // ) + Box::new([].into_iter()) + } + + fn primary_monitor(&self) -> Option { + // NOTE: There's no such concept on Wayland. + None + } + + /// Get the raw-window-handle v0.6 display handle. + fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { + self + } + + /// Get the raw-window-handle v0.6 window handle. + fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { + self + } +} + +impl Drop for Popup { + fn drop(&mut self) { + self.window_requests.closed.store(true, Ordering::Relaxed); + self.event_loop_awakener.ping(); + } +} + +impl rwh_06::HasWindowHandle for Popup { + fn window_handle(&self) -> Result, rwh_06::HandleError> { + let raw = rwh_06::WaylandWindowHandle::new({ + let state = self.popup_state.lock().unwrap(); + let ptr = state.window.wl_surface().id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") + }); + + unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) } + } +} + +impl rwh_06::HasDisplayHandle for Popup { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + let raw = rwh_06::WaylandDisplayHandle::new({ + let ptr = self.display.id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null") + }); + + unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) } + } +} diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 9f5eda4037..5624aff8cb 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -16,13 +16,13 @@ use sctk::seat::SeatState; use sctk::seat::pointer::ThemedPointer; use sctk::shell::WaylandSurface; use sctk::shell::xdg::XdgShell; +use sctk::shell::xdg::popup::{Popup as XdgPopup, PopupConfigure, PopupHandler}; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler}; use sctk::shm::slot::SlotPool; use sctk::shm::{Shm, ShmHandler}; use sctk::subcompositor::SubcompositorState; use winit_core::error::OsError; -use crate::WindowId; use crate::event_loop::sink::EventSink; use crate::output::MonitorHandle; use crate::seat::{ @@ -36,6 +36,7 @@ use crate::types::wp_viewporter::ViewporterState; use crate::types::xdg_activation::XdgActivationState; use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState; use crate::window::{WindowRequests, WindowState}; +use crate::{WindowId, popup}; /// Winit's Wayland state. #[derive(Debug)] @@ -292,24 +293,24 @@ impl WindowHandler for WinitState { ) { let window_id = super::make_wid(window.wl_surface()); - let pos = if let Some(pos) = + let index = if let Some(index) = self.window_compositor_updates.iter().position(|update| update.window_id == window_id) { - pos + index } else { self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Populate the configure to the window. - self.window_compositor_updates[pos].resized |= self + self.window_compositor_updates[index].resized |= self .windows .get_mut() .get_mut(&window_id) .expect("got configure for dead window.") .lock() .unwrap() - .configure(configure, &self.shm, &self.subcompositor_state); + .configure_window(configure, &self.shm, &self.subcompositor_state); // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the // users, since it can break a lot of things, thus it'll ask users to redraw instead. @@ -325,6 +326,57 @@ impl WindowHandler for WinitState { } } +impl PopupHandler for WinitState { + fn configure( + &mut self, + _: &Connection, + _: &QueueHandle, + popup: &XdgPopup, + configure: PopupConfigure, + ) { + let window_id = super::make_wid(popup.wl_surface()); + println!("Finished configuring the popup: {:?}", window_id); + + // let index = + if let Some(index) = + self.window_compositor_updates.iter().position(|update| update.window_id == window_id) + { + index + } else { + self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + // Populate the configure to the window. + // self.window_compositor_updates[index].resized |= false; + self.windows + .get_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap() + .configure_popup(configure); + + // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the + // users, since it can break a lot of things, thus it'll ask users to redraw instead + self.window_requests + .get_mut() + .get(&window_id) + .unwrap() + .redraw_requested + .store(true, Ordering::Relaxed); + + // Manually mark that we've got an event, since configure may not generate a resize. + self.dispatched_events = true; + } + + fn done(&mut self, _: &Connection, _: &QueueHandle, popup: &XdgPopup) { + let window_id = super::make_wid(popup.wl_surface()); + println!("Destroying popup with id: {:?}", window_id); + Self::queue_close(&mut self.window_compositor_updates, window_id); + } +} + impl OutputHandler for WinitState { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state @@ -451,4 +503,5 @@ sctk::delegate_output!(WinitState); sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); +sctk::delegate_xdg_popup!(WinitState); sctk::delegate_xdg_window!(WinitState); diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index 81849be78e..7fcae63de9 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -6,6 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; +use rwh_06::RawWindowHandle; use sctk::compositor::{CompositorState, Region, SurfaceData}; use sctk::reexports::client::protocol::wl_display::WlDisplay; use sctk::reexports::client::protocol::wl_surface::WlSurface; @@ -30,10 +31,9 @@ use super::event_loop::sink::EventSink; use super::output::MonitorHandle; use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; +use crate::window::state::WindowType; use crate::{WindowAttributesWayland, output}; - pub(crate) mod state; - pub use state::WindowState; /// The Wayland window. @@ -112,14 +112,29 @@ impl Window { .and_then(|p| p.cast::().ok()) .unwrap_or_default(); + let mut scale_factor = None; + if let Some(handle) = attributes.parent_window() { + if let RawWindowHandle::Wayland(handle) = handle { + if let Some(s) = state + .windows + .borrow() + .get(&WindowId::from_raw(handle.surface.as_ptr() as usize)) + { + scale_factor = Some(s.lock().unwrap().scale_factor()); + } + } + } + let scale_factor = scale_factor.unwrap_or(1.0); + let mut window_state = WindowState::new( event_loop_window_target.handle.clone(), &event_loop_window_target.queue_handle, &state, size, - window.clone(), + state::WindowType::Window((window.clone(), None)), attributes.preferred_theme, prefer_csd, + scale_factor, ); window_state.set_window_icon(attributes.window_icon); @@ -448,13 +463,14 @@ impl CoreWindow for Window { } fn is_maximized(&self) -> bool { - self.window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_maximized()) - .unwrap_or_default() + if let WindowType::Window((_, last_configure)) = &self.window_state.lock().unwrap().window { + last_configure + .as_ref() + .map(|last_configure| last_configure.is_maximized()) + .unwrap_or_default() + } else { + false + } } fn set_fullscreen(&self, fullscreen: Option) { @@ -474,14 +490,16 @@ impl CoreWindow for Window { } fn fullscreen(&self) -> Option { - let is_fullscreen = self - .window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_fullscreen()) - .unwrap_or_default(); + let is_fullscreen = if let WindowType::Window((_, last_configure)) = + &self.window_state.lock().unwrap().window + { + last_configure + .as_ref() + .map(|last_configure| last_configure.is_fullscreen()) + .unwrap_or_default() + } else { + false + }; if is_fullscreen { let current_monitor = self.current_monitor(); diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index 3a1a9b5275..8b00349ab6 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -23,6 +23,8 @@ use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as use sctk::seat::pointer::{PointerDataExt, ThemedPointer}; use sctk::shell::WaylandSurface; use sctk::shell::xdg::XdgSurface; +use sctk::shell::xdg::popup::{Popup, PopupConfigure}; +use sctk::shell::xdg::XdgPositioner; use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; use sctk::shm::Shm; use sctk::shm::slot::SlotPool; @@ -37,7 +39,6 @@ use winit_core::window::{ }; use crate::event_loop::OwnedDisplayHandle; -use crate::logical_to_physical_rounded; use crate::seat::{ PointerConstraintsState, TextInputClientState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, @@ -46,6 +47,7 @@ use crate::state::{WindowCompositorUpdate, WinitState}; use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor}; use crate::types::kwin_blur::KWinBlurManager; use crate::types::xdg_toplevel_icon_manager::ToplevelIcon; +use crate::{logical_to_physical_rounded, popup}; #[cfg(feature = "sctk-adwaita")] pub type WinitFrame = sctk_adwaita::AdwaitaFrame; @@ -55,6 +57,40 @@ pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame = LogicalSize::new(2, 1); +#[derive(Debug)] +pub enum WindowType { + // The option is the last received configure + Window((Window, Option)), + Popup((Popup, XdgPositioner, Option)), +} + +impl WindowType { + pub fn is_configured(&self) -> bool { + match self { + Self::Window((_, last_configure)) => last_configure.is_some(), + Self::Popup((_, _, last_configure)) => last_configure.is_some(), + } + } +} + +impl WaylandSurface for WindowType { + fn wl_surface(&self) -> &wayland_client::protocol::wl_surface::WlSurface { + match self { + Self::Window((window, _)) => window.wl_surface(), + Self::Popup((popup, _, _)) => popup.wl_surface(), + } + } +} + +impl XdgSurface for WindowType { + fn xdg_surface(&self) -> &wayland_protocols::xdg::shell::client::xdg_surface::XdgSurface { + match self { + Self::Window((window, _)) => window.xdg_surface(), + Self::Popup((popup, _, _)) => popup.xdg_surface(), + } + } +} + /// The state of the window which is being updated from the [`WinitState`]. #[derive(Debug)] pub struct WindowState { @@ -64,9 +100,6 @@ pub struct WindowState { /// The `Shm` to set cursor. pub shm: WlShm, - /// The last received configure. - pub last_configure: Option, - /// The pointers observed on the window. pub pointers: Vec>>, @@ -165,7 +198,7 @@ pub struct WindowState { has_pending_move: Option, /// The underlying SCTK window. - pub window: Window, + pub window: WindowType, // NOTE: The spec says that destroying parent(`window` in our case), will unmap the // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations @@ -182,9 +215,10 @@ impl WindowState { queue_handle: &QueueHandle, winit_state: &WinitState, initial_size: Size, - window: Window, + window: WindowType, theme: Option, prefer_csd: bool, + scale_factor: f64, ) -> Self { let compositor = winit_state.compositor_state.clone(); let pointer_constraints = winit_state.pointer_constraints.clone(); @@ -221,7 +255,6 @@ impl WindowState { seat_focus: Default::default(), has_pending_move: None, text_input_state: None, - last_configure: None, max_surface_size: None, min_surface_size: MIN_WINDOW_SIZE, resize_increments: None, @@ -229,7 +262,7 @@ impl WindowState { pointers: Default::default(), queue_handle: queue_handle.clone(), resizable: true, - scale_factor: 1., + scale_factor, shm: winit_state.shm.wl_shm().clone(), image_pool: winit_state.image_pool.clone(), size: initial_size.to_logical(1.), @@ -281,8 +314,39 @@ impl WindowState { FrameCallbackState::Requested => (), } } + pub fn configure_popup(&mut self, configure: PopupConfigure) { + // NOTE: when using fractional scaling or wl_compositor@v6 the scaling + // should be delivered before the first configure, thus apply it to + // properly scale the physical sizes provided by the users. + if let Some(initial_size) = self.initial_size.take() { + self.size = initial_size.to_logical(self.scale_factor()); + } + + // The popup was constrained to a different size by the compositor + assert!(configure.width >= 0); + assert!(configure.height >= 0); + let constrained = self.size.width != configure.width as u32 + || self.size.height != configure.height as u32; + let new_size = LogicalSize { + width: (configure.width as u32).into(), + height: (configure.height as u32).into(), + }; + + // NOTE: Set the configure before doing a resize, since we query it during it. + if let WindowType::Popup((_, _, last_configure)) = &mut self.window { + *last_configure = Some(configure) + } else { + // This should never happen + assert!(false); + return; + } + + if constrained { + self.resize(new_size); + } + } - pub fn configure( + pub fn configure_window( &mut self, configure: WindowConfigure, shm: &Shm, @@ -400,26 +464,32 @@ impl WindowState { } let new_state = configure.state; - let old_state = self.last_configure.as_ref().map(|configure| configure.state); - - let state_change_requires_resize = old_state - .map(|old_state| { - !old_state - .symmetric_difference(new_state) - .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) - .is_empty() - }) - // NOTE: `None` is present for the initial configure, thus we must always resize. - .unwrap_or(true); - - // NOTE: Set the configure before doing a resize, since we query it during it. - self.last_configure = Some(configure); - - if state_change_requires_resize || new_size != self.surface_size() { - self.resize(new_size); - true + if let WindowType::Window((_, last_configure)) = &mut self.window { + let old_state = last_configure.as_ref().map(|configure| configure.state); + + let state_change_requires_resize = old_state + .map(|old_state| { + !old_state + .symmetric_difference(new_state) + .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) + .is_empty() + }) + // NOTE: `None` is present for the initial configure, thus we must always resize. + .unwrap_or(true); + + // NOTE: Set the configure before doing a resize, since we query it during it. + *last_configure = Some(configure); + + if state_change_requires_resize || new_size != self.surface_size() { + self.resize(new_size); + true + } else { + false + } } else { - false + // This should never happen + assert!(false); + return false; } } @@ -451,29 +521,40 @@ impl WindowState { /// Start interacting drag resize. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); - - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction)); - }); - - Ok(()) + if let WindowType::Window((window, _)) = &self.window { + let xdg_toplevel = window.xdg_toplevel(); + + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction)); + }); + Ok(()) + } else { + // Popup + Err(RequestError::NotSupported(NotSupportedError::new( + "Drag resize for popup not supported", + ))) + } } /// Start the window drag. pub fn drag_window(&self) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - xdg_toplevel._move(seat, serial); - }); - - Ok(()) + if let WindowType::Window((window, _)) = &self.window { + let xdg_toplevel = window.xdg_toplevel(); + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel._move(seat, serial); + }); + + Ok(()) + } else { + // Popup + Err(RequestError::NotSupported(NotSupportedError::new("Drag for popup not supported"))) + } } /// Tells whether the window should be closed. @@ -488,32 +569,36 @@ impl WindowState { window_id: WindowId, updates: &mut Vec, ) -> Option { - match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { - FrameAction::Minimize => self.window.set_minimized(), - FrameAction::Maximize => self.window.set_maximized(), - FrameAction::UnMaximize => self.window.unset_maximized(), - FrameAction::Close => WinitState::queue_close(updates, window_id), - FrameAction::Move => self.has_pending_move = Some(serial), - FrameAction::Resize(edge) => { - let edge = match edge { - ResizeEdge::None => XdgResizeEdge::None, - ResizeEdge::Top => XdgResizeEdge::Top, - ResizeEdge::Bottom => XdgResizeEdge::Bottom, - ResizeEdge::Left => XdgResizeEdge::Left, - ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, - ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, - ResizeEdge::Right => XdgResizeEdge::Right, - ResizeEdge::TopRight => XdgResizeEdge::TopRight, - ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, - _ => return None, - }; - self.window.resize(seat, serial, edge); - }, - FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), - _ => (), - }; + if let WindowType::Window((window, _)) = &self.window { + match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { + FrameAction::Minimize => window.set_minimized(), + FrameAction::Maximize => window.set_maximized(), + FrameAction::UnMaximize => window.unset_maximized(), + FrameAction::Close => WinitState::queue_close(updates, window_id), + FrameAction::Move => self.has_pending_move = Some(serial), + FrameAction::Resize(edge) => { + let edge = match edge { + ResizeEdge::None => XdgResizeEdge::None, + ResizeEdge::Top => XdgResizeEdge::Top, + ResizeEdge::Bottom => XdgResizeEdge::Bottom, + ResizeEdge::Left => XdgResizeEdge::Left, + ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, + ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, + ResizeEdge::Right => XdgResizeEdge::Right, + ResizeEdge::TopRight => XdgResizeEdge::TopRight, + ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, + _ => return None, + }; + window.resize(seat, serial, edge); + }, + FrameAction::ShowMenu(x, y) => window.show_window_menu(seat, serial, (x, y)), + _ => (), + }; - Some(false) + Some(false) + } else { + None + } } pub fn frame_point_left(&mut self) { @@ -531,18 +616,22 @@ impl WindowState { x: f64, y: f64, ) -> Option { - // Take the serial if we had any, so it doesn't stick around. - let serial = self.has_pending_move.take(); - - if let Some(frame) = self.frame.as_mut() { - let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); - // If we have a cursor change, that means that cursor is over the decorations, - // so try to apply move. - if let Some(serial) = cursor.is_some().then_some(serial).flatten() { - self.window.move_(seat, serial); - None + if let WindowType::Window((window, _)) = &self.window { + // Take the serial if we had any, so it doesn't stick around. + let serial = self.has_pending_move.take(); + + if let Some(frame) = self.frame.as_mut() { + let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); + // If we have a cursor change, that means that cursor is over the decorations, + // so try to apply move. + if let Some(serial) = cursor.is_some().then_some(serial).flatten() { + window.move_(seat, serial); + None + } else { + cursor + } } else { - cursor + None } } else { None @@ -606,21 +695,24 @@ impl WindowState { /// Whether the window received initial configure event from the compositor. #[inline] pub fn is_configured(&self) -> bool { - self.last_configure.is_some() + self.window.is_configured() } #[inline] pub fn is_decorated(&mut self) -> bool { - let csd = self - .last_configure - .as_ref() - .map(|configure| configure.decoration_mode == DecorationMode::Client) - .unwrap_or(false); - if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { - !frame.is_hidden() + if let WindowType::Window((_, last_configure)) = &mut self.window { + let csd = last_configure + .as_ref() + .map(|configure| configure.decoration_mode == DecorationMode::Client) + .unwrap_or(false); + if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { + !frame.is_hidden() + } else { + // Server side decorations. + true + } } else { - // Server side decorations. - true + false } } @@ -633,6 +725,13 @@ impl WindowState { .unwrap_or(self.size) } + /// Get the origin of the content surface by considering the client side decoration if available + /// This is required for example when creating a popup, because as parent a xdg_surface must be + /// passed but the frame is only a wl_surface + pub fn content_surface_origin(&self) -> LogicalPosition { + self.frame.as_ref().map(|frame| frame.location().into()).unwrap_or_else(|| (0, 0).into()) + } + /// Register pointer on the top-level. pub fn pointer_entered(&mut self, added: Weak>) { self.pointers.push(added); @@ -695,8 +794,17 @@ impl WindowState { /// Try to resize the window when the user can do so. pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize { - if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { - self.resize(surface_size.to_logical(self.scale_factor())) + match &self.window { + WindowType::Window((_, last_configure)) => { + if last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { + self.resize(surface_size.to_logical(self.scale_factor())) + } + } + WindowType::Popup((popup, positioner, _)) => { + let size = surface_size.to_logical(self.scale_factor()); + positioner.set_size(size.width, size.height); + popup.reposition(positioner, 0); + } } logical_to_physical_rounded(self.surface_size(), self.scale_factor()) @@ -707,8 +815,10 @@ impl WindowState { self.size = surface_size; // Update the stateless size. - if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { - self.stateless_size = surface_size; + if let WindowType::Window((_, last_configure)) = &mut self.window { + if Some(true) == last_configure.as_ref().map(Self::is_stateless) { + self.stateless_size = surface_size; + } } // Update the inner frame. @@ -837,33 +947,37 @@ impl WindowState { /// Set maximum inner window size. pub fn set_min_surface_size(&mut self, size: Option>) { - // Ensure that the window has the right minimum size. - let mut size = size.unwrap_or(MIN_WINDOW_SIZE); - size.width = size.width.max(MIN_WINDOW_SIZE.width); - size.height = size.height.max(MIN_WINDOW_SIZE.height); - - // Add the borders. - let size = self - .frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size); + if let WindowType::Window((window, _)) = &self.window { + // Ensure that the window has the right minimum size. + let mut size = size.unwrap_or(MIN_WINDOW_SIZE); + size.width = size.width.max(MIN_WINDOW_SIZE.width); + size.height = size.height.max(MIN_WINDOW_SIZE.height); + + // Add the borders. + let size = self + .frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size); - self.min_surface_size = size; - self.window.set_min_size(Some(size.into())); + self.min_surface_size = size; + window.set_min_size(Some(size.into())); + } } /// Set maximum inner window size. pub fn set_max_surface_size(&mut self, size: Option>) { - let size = size.map(|size| { - self.frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size) - }); - - self.max_surface_size = size; - self.window.set_max_size(size.map(Into::into)); + if let WindowType::Window((window, _)) = &self.window { + let size = size.map(|size| { + self.frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size) + }); + + self.max_surface_size = size; + window.set_max_size(size.map(Into::into)); + } } /// Set the CSD theme. @@ -960,12 +1074,14 @@ impl WindowState { } pub fn show_window_menu(&self, position: LogicalPosition) { - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - self.window.show_window_menu(seat, serial, position.into()); - }); + if let WindowType::Window((window, _)) = &self.window { + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + window.show_window_menu(seat, serial, position.into()); + }); + } } /// Set the position of the cursor. @@ -1016,22 +1132,24 @@ impl WindowState { self.decorate = decorate; - match self.last_configure.as_ref().map(|configure| configure.decoration_mode) { - Some(DecorationMode::Server) if !self.decorate => { - // To disable decorations we should request client and hide the frame. - self.window.request_decoration_mode(Some(DecorationMode::Client)) - }, - _ if self.decorate && self.prefer_csd => { - self.window.request_decoration_mode(Some(DecorationMode::Client)) - }, - _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)), - _ => (), - } + if let WindowType::Window((window, last_configure)) = &self.window { + match last_configure.as_ref().map(|configure| configure.decoration_mode) { + Some(DecorationMode::Server) if !self.decorate => { + // To disable decorations we should request client and hide the frame. + window.request_decoration_mode(Some(DecorationMode::Client)) + }, + _ if self.decorate && self.prefer_csd => { + window.request_decoration_mode(Some(DecorationMode::Client)) + }, + _ if self.decorate => window.request_decoration_mode(Some(DecorationMode::Server)), + _ => (), + } - if let Some(frame) = self.frame.as_mut() { - frame.set_hidden(!decorate); - // Force the resize. - self.resize(self.size); + if let Some(frame) = self.frame.as_mut() { + frame.set_hidden(!decorate); + // Force the resize. + self.resize(self.size); + } } } @@ -1149,46 +1267,50 @@ impl WindowState { frame.set_title(&title); } - self.window.set_title(&title); + if let WindowType::Window((window, _)) = &self.window { + window.set_title(&title); + } self.title = title; } /// Set the window's icon pub fn set_window_icon(&mut self, window_icon: Option) { - let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() { - Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager, - None => { - warn!("`xdg_toplevel_icon_manager_v1` is not supported"); - return; - }, - }; + if let WindowType::Window((window, _)) = &self.window { + let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() { + Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager, + None => { + warn!("`xdg_toplevel_icon_manager_v1` is not supported"); + return; + }, + }; - let (toplevel_icon, xdg_toplevel_icon) = match window_icon { - Some(icon) => { - let mut image_pool = self.image_pool.lock().unwrap(); - let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) { - Ok(toplevel_icon) => toplevel_icon, - Err(error) => { - warn!("Error setting window icon: {error}"); - return; - }, - }; + let (toplevel_icon, xdg_toplevel_icon) = match window_icon { + Some(icon) => { + let mut image_pool = self.image_pool.lock().unwrap(); + let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) { + Ok(toplevel_icon) => toplevel_icon, + Err(error) => { + warn!("Error setting window icon: {error}"); + return; + }, + }; - let xdg_toplevel_icon = - xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData); + let xdg_toplevel_icon = + xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData); - toplevel_icon.add_buffer(&xdg_toplevel_icon); + toplevel_icon.add_buffer(&xdg_toplevel_icon); - (Some(toplevel_icon), Some(xdg_toplevel_icon)) - }, - None => (None, None), - }; + (Some(toplevel_icon), Some(xdg_toplevel_icon)) + }, + None => (None, None), + }; - xdg_toplevel_icon_manager.set_icon(self.window.xdg_toplevel(), xdg_toplevel_icon.as_ref()); - self.toplevel_icon = toplevel_icon; + xdg_toplevel_icon_manager.set_icon(window.xdg_toplevel(), xdg_toplevel_icon.as_ref()); + self.toplevel_icon = toplevel_icon; - if let Some(xdg_toplevel_icon) = xdg_toplevel_icon { - xdg_toplevel_icon.destroy(); + if let Some(xdg_toplevel_icon) = xdg_toplevel_icon { + xdg_toplevel_icon.destroy(); + } } }