From dc973883c9a4ddceb26b095ee78b793beaffebe9 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 11 Aug 2023 02:30:55 -0700 Subject: [PATCH] Add a way to embed the X11 window into another Signed-off-by: John Nunley Tested-by: Kirill Chibisov --- CHANGELOG.md | 1 + examples/x11_embed.rs | 69 +++++++++++++++++++++++++++ src/platform/x11.rs | 36 ++++++++++++-- src/platform_impl/linux/mod.rs | 30 +++++++----- src/platform_impl/linux/x11/atoms.rs | 3 +- src/platform_impl/linux/x11/window.rs | 56 +++++++++++++++------- 6 files changed, 160 insertions(+), 35 deletions(-) create mode 100644 examples/x11_embed.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d9cd90f6..16c00a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer. +- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window. # 0.29.0-beta.0 diff --git a/examples/x11_embed.rs b/examples/x11_embed.rs new file mode 100644 index 00000000..37014ea9 --- /dev/null +++ b/examples/x11_embed.rs @@ -0,0 +1,69 @@ +//! A demonstration of embedding a winit window in an existing X11 application. + +#[cfg(x11_platform)] +#[path = "util/fill.rs"] +mod fill; + +#[cfg(x11_platform)] +mod imple { + use super::fill; + use simple_logger::SimpleLogger; + use winit::{ + event::{Event, WindowEvent}, + event_loop::EventLoop, + platform::x11::WindowBuilderExtX11, + window::WindowBuilder, + }; + + pub(super) fn entry() -> Result<(), Box> { + // First argument should be a 32-bit X11 window ID. + let parent_window_id = std::env::args() + .nth(1) + .ok_or("Expected a 32-bit X11 window ID as the first argument.")? + .parse::()?; + + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("An embedded window!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .with_embed_parent_window(parent_window_id) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + control_flow.set_wait(); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window_id == window.id() => control_flow.set_exit(), + Event::AboutToWait => { + window.request_redraw(); + } + Event::RedrawRequested(_) => { + // Notify the windowing system that we'll be presenting to the window. + window.pre_present_notify(); + fill::fill_window(&window); + } + _ => (), + } + })?; + + Ok(()) + } +} + +#[cfg(not(x11_platform))] +mod imple { + pub(super) fn entry() -> Result<(), Box> { + println!("This example is only supported on X11 platforms."); + Ok(()) + } +} + +fn main() -> Result<(), Box> { + imple::entry() +} diff --git a/src/platform/x11.rs b/src/platform/x11.rs index 95b2534a..51f63b9f 100644 --- a/src/platform/x11.rs +++ b/src/platform/x11.rs @@ -20,6 +20,9 @@ pub type XlibErrorHook = /// A unique identifer for an X11 visual. pub type XVisualID = u32; +/// A unique identifier for an X11 window. +pub type XWindow = u32; + /// Hook to winit's xlib error handling callback. /// /// This method is provided as a safe way to handle the errors comming from X11 @@ -118,18 +121,35 @@ pub trait WindowBuilderExtX11 { /// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200)); /// ``` fn with_base_size>(self, base_size: S) -> Self; + + /// Embed this window into another parent window. + /// + /// # Example + /// + /// ```no_run + /// use winit::window::WindowBuilder; + /// use winit::platform::x11::{XWindow, WindowBuilderExtX11}; + /// # fn main() -> Result<(), Box> { + /// let event_loop = winit::event_loop::EventLoop::new(); + /// let parent_window_id = std::env::args().nth(1).unwrap().parse::()?; + /// let window = WindowBuilder::new() + /// .with_embed_parent_window(parent_window_id) + /// .build(&event_loop)?; + /// # Ok(()) } + /// ``` + fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self; } impl WindowBuilderExtX11 for WindowBuilder { #[inline] fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { - self.platform_specific.visual_id = Some(visual_id); + self.platform_specific.x11.visual_id = Some(visual_id); self } #[inline] fn with_x11_screen(mut self, screen_id: i32) -> Self { - self.platform_specific.screen_id = Some(screen_id); + self.platform_specific.x11.screen_id = Some(screen_id); self } @@ -141,19 +161,25 @@ impl WindowBuilderExtX11 for WindowBuilder { #[inline] fn with_override_redirect(mut self, override_redirect: bool) -> Self { - self.platform_specific.override_redirect = override_redirect; + self.platform_specific.x11.override_redirect = override_redirect; self } #[inline] fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { - self.platform_specific.x11_window_types = x11_window_types; + self.platform_specific.x11.x11_window_types = x11_window_types; self } #[inline] fn with_base_size>(mut self, base_size: S) -> Self { - self.platform_specific.base_size = Some(base_size.into()); + self.platform_specific.x11.base_size = Some(base_size.into()); + self + } + + #[inline] + fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self { + self.platform_specific.x11.embed_window = Some(parent_window_id); self } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index dedc3c51..611f73a7 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -96,15 +96,20 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub name: Option, pub activation_token: Option, #[cfg(x11_platform)] + pub x11: X11WindowBuilderAttributes, +} + +#[derive(Clone)] +#[cfg(x11_platform)] +pub struct X11WindowBuilderAttributes { pub visual_id: Option, - #[cfg(x11_platform)] pub screen_id: Option, - #[cfg(x11_platform)] pub base_size: Option, - #[cfg(x11_platform)] pub override_redirect: bool, - #[cfg(x11_platform)] pub x11_window_types: Vec, + + /// The parent window to embed this window into. + pub embed_window: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -113,15 +118,14 @@ impl Default for PlatformSpecificWindowBuilderAttributes { name: None, activation_token: None, #[cfg(x11_platform)] - visual_id: None, - #[cfg(x11_platform)] - screen_id: None, - #[cfg(x11_platform)] - base_size: None, - #[cfg(x11_platform)] - override_redirect: false, - #[cfg(x11_platform)] - x11_window_types: vec![XWindowType::Normal], + x11: X11WindowBuilderAttributes { + visual_id: None, + screen_id: None, + base_size: None, + override_redirect: false, + x11_window_types: vec![XWindowType::Normal], + embed_window: None, + }, } } } diff --git a/src/platform_impl/linux/x11/atoms.rs b/src/platform_impl/linux/x11/atoms.rs index 48c7f1ed..bfdef98e 100644 --- a/src/platform_impl/linux/x11/atoms.rs +++ b/src/platform_impl/linux/x11/atoms.rs @@ -99,7 +99,8 @@ atom_manager! { _NET_CLIENT_LIST, _NET_FRAME_EXTENTS, _NET_SUPPORTED, - _NET_SUPPORTING_WM_CHECK + _NET_SUPPORTING_WM_CHECK, + _XEMBED } impl Index for Atoms { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index d9082a97..1e24a06a 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -126,6 +126,15 @@ pub(crate) struct UnownedWindow { activation_sender: WakeSender, } +macro_rules! leap { + ($e:expr) => { + match $e { + Ok(x) => x, + Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))), + } + }; +} + impl UnownedWindow { #[allow(clippy::unnecessary_cast)] pub(crate) fn new( @@ -133,15 +142,6 @@ impl UnownedWindow { window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { - macro_rules! leap { - ($e:expr) => { - match $e { - Ok(x) => x, - Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))), - } - }; - } - let xconn = &event_loop.xconn; let atoms = xconn.atoms(); let root = match window_attrs.parent_window { @@ -210,7 +210,7 @@ impl UnownedWindow { dimensions }; - let screen_id = match pl_attribs.screen_id { + let screen_id = match pl_attribs.x11.screen_id { Some(id) => id, None => xconn.default_screen_index() as c_int, }; @@ -230,7 +230,7 @@ impl UnownedWindow { }); // creating - let (visualtype, depth, require_colormap) = match pl_attribs.visual_id { + let (visualtype, depth, require_colormap) = match pl_attribs.x11.visual_id { Some(vi) => { // Find this specific visual. let (visualtype, depth) = all_visuals @@ -271,12 +271,12 @@ impl UnownedWindow { aux = aux.event_mask(event_mask).border_pixel(0); - if pl_attribs.override_redirect { + if pl_attribs.x11.override_redirect { aux = aux.override_redirect(true as u32); } // Add a colormap if needed. - let colormap_visual = match pl_attribs.visual_id { + let colormap_visual = match pl_attribs.x11.visual_id { Some(vi) => Some(vi), None if require_colormap => Some(visual), _ => None, @@ -298,6 +298,9 @@ impl UnownedWindow { aux }; + // Figure out the window's parent. + let parent = pl_attribs.x11.embed_window.unwrap_or(root); + // finally creating the window let xwindow = { let (x, y) = position.map_or((0, 0), Into::into); @@ -305,7 +308,7 @@ impl UnownedWindow { let result = xconn.xcb_connection().create_window( depth, wid, - root, + parent, x, y, dimensions.0.try_into().unwrap(), @@ -357,6 +360,11 @@ impl UnownedWindow { leap!(window.set_theme_inner(Some(theme))).ignore_error(); } + // Embed the window if needed. + if pl_attribs.x11.embed_window.is_some() { + window.embed_window()?; + } + { // Enable drag and drop (TODO: extend API to make this toggleable) { @@ -407,7 +415,7 @@ impl UnownedWindow { flusher.ignore_error() } - leap!(window.set_window_types(pl_attribs.x11_window_types)).ignore_error(); + leap!(window.set_window_types(pl_attribs.x11.x11_window_types)).ignore_error(); // Set size hints. let mut min_inner_size = window_attrs @@ -430,7 +438,7 @@ impl UnownedWindow { shared_state.min_inner_size = min_inner_size.map(Into::into); shared_state.max_inner_size = max_inner_size.map(Into::into); shared_state.resize_increments = window_attrs.resize_increments; - shared_state.base_size = pl_attribs.base_size; + shared_state.base_size = pl_attribs.x11.base_size; let normal_hints = WmSizeHints { position: position.map(|PhysicalPosition { x, y }| { @@ -447,6 +455,7 @@ impl UnownedWindow { .resize_increments .map(|size| cast_size_to_hint(size, scale_factor)), base_size: pl_attribs + .x11 .base_size .map(|size| cast_size_to_hint(size, scale_factor)), aspect: None, @@ -559,6 +568,21 @@ impl UnownedWindow { Ok(window) } + /// Embed this window into a parent window. + pub(super) fn embed_window(&self) -> Result<(), RootOsError> { + let atoms = self.xconn.atoms(); + leap!(leap!(self.xconn.change_property( + self.xwindow, + atoms[_XEMBED], + atoms[_XEMBED], + xproto::PropMode::REPLACE, + &[0u32, 1u32], + )) + .check()); + + Ok(()) + } + pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> { self.shared_state.lock().unwrap() }