Skip to main content

wingy_x11/
lib.rs

1//! # X11
2
3#![warn(clippy::exhaustive_enums)]
4
5#[macro_use]
6extern crate tracing;
7
8use dpi::Size;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11use wingy_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
12use wingy_core::window::{ActivationToken, PlatformWindowAttributes, Window as CoreWindow};
13
14pub use crate::event_loop::{ActiveEventLoop, EventLoop};
15pub use crate::window::Window;
16
17macro_rules! os_error {
18    ($error:expr) => {{ wingy_core::error::OsError::new(line!(), file!(), $error) }};
19}
20
21mod activation;
22mod atoms;
23mod data_transfer;
24mod event_loop;
25mod event_processor;
26pub mod ffi;
27mod ime;
28mod monitor;
29mod util;
30mod window;
31mod xdisplay;
32mod xsettings;
33
34pub use data_transfer::{Selection, SelectionReader, SelectionType};
35
36/// X window type. Maps directly to
37/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
38#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40#[non_exhaustive]
41pub enum WindowType {
42    /// A desktop feature. This can include a single window containing desktop icons with the same
43    /// dimensions as the screen, allowing the desktop environment to have full control of the
44    /// desktop, without the need for proxying root window clicks.
45    Desktop,
46    /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all
47    /// other windows.
48    Dock,
49    /// Toolbar windows. "Torn off" from the main application.
50    Toolbar,
51    /// Pinnable menu windows. "Torn off" from the main application.
52    Menu,
53    /// A small persistent utility window, such as a palette or toolbox.
54    Utility,
55    /// The window is a splash screen displayed as an application is starting up.
56    Splash,
57    /// This is a dialog window.
58    Dialog,
59    /// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
60    /// This property is typically used on override-redirect windows.
61    DropdownMenu,
62    /// A popup menu that usually appears when the user right clicks on an object.
63    /// This property is typically used on override-redirect windows.
64    PopupMenu,
65    /// A tooltip window. Usually used to show additional information when hovering over an object
66    /// with the cursor. This property is typically used on override-redirect windows.
67    Tooltip,
68    /// The window is a notification.
69    /// This property is typically used on override-redirect windows.
70    Notification,
71    /// This should be used on the windows that are popped up by combo boxes.
72    /// This property is typically used on override-redirect windows.
73    Combo,
74    /// This indicates the window is being dragged.
75    /// This property is typically used on override-redirect windows.
76    Dnd,
77    /// This is a normal, top-level window.
78    #[default]
79    Normal,
80}
81
82/// The first argument in the provided hook will be the pointer to `XDisplay`
83/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
84/// indicator whether the error was handled by the callback.
85///
86/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
87pub type XlibErrorHook =
88    Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
89
90/// A unique identifier for an X11 visual.
91pub type XVisualID = u32;
92
93/// A unique identifier for an X11 window.
94pub type XWindow = u32;
95
96/// Hook to wingy's xlib error handling callback.
97///
98/// This method is provided as a safe way to handle the errors coming from X11
99/// when using xlib in external crates, like glutin for GLX access. Trying to
100/// handle errors by speculating with `XSetErrorHandler` is [`unsafe`].
101///
102/// **Be aware that your hook is always invoked and returning `true` from it will
103/// prevent `wingy` from getting the error itself. It's wise to always return
104/// `false` if you're not initiated the `Sync`.**
105///
106/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
107#[inline]
108pub fn register_xlib_error_hook(hook: XlibErrorHook) {
109    // Append new hook.
110    crate::event_loop::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
111}
112
113/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
114///
115/// [`ActiveEventLoop`]: wingy_core::event_loop::ActiveEventLoop
116pub trait ActiveEventLoopExtX11 {
117    /// True if the event loop uses X11.
118    fn is_x11(&self) -> bool;
119}
120
121impl ActiveEventLoopExtX11 for dyn CoreActiveEventLoop + '_ {
122    #[inline]
123    fn is_x11(&self) -> bool {
124        self.cast_ref::<ActiveEventLoop>().is_some()
125    }
126}
127
128/// Additional methods on [`EventLoop`] that are specific to X11.
129pub trait EventLoopExtX11 {
130    /// True if the [`EventLoop`] uses X11.
131    fn is_x11(&self) -> bool;
132}
133
134/// Additional methods when building event loop that are specific to X11.
135pub trait EventLoopBuilderExtX11 {
136    /// Force using X11.
137    fn with_x11(&mut self) -> &mut Self;
138
139    /// Whether to allow the event loop to be created off of the main thread.
140    ///
141    /// By default, the window is only allowed to be created on the main
142    /// thread, to make platform compatibility easier.
143    fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
144}
145
146/// Additional methods on [`Window`] that are specific to X11.
147///
148/// [`Window`]: crate::window::Window
149pub trait WindowExtX11 {}
150
151impl WindowExtX11 for dyn CoreWindow {}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub(crate) struct ApplicationName {
155    pub(crate) general: String,
156    pub(crate) instance: String,
157}
158
159#[derive(Clone, Debug)]
160pub struct WindowAttributesX11 {
161    pub(crate) name: Option<ApplicationName>,
162    pub(crate) activation_token: Option<ActivationToken>,
163    pub(crate) visual_id: Option<XVisualID>,
164    pub(crate) screen_id: Option<i32>,
165    pub(crate) base_size: Option<Size>,
166    pub(crate) override_redirect: bool,
167    pub(crate) x11_window_types: Vec<WindowType>,
168
169    /// The parent window to embed this window into.
170    pub(crate) embed_window: Option<XWindow>,
171}
172
173impl Default for WindowAttributesX11 {
174    fn default() -> Self {
175        Self {
176            name: None,
177            activation_token: None,
178            visual_id: None,
179            screen_id: None,
180            base_size: None,
181            override_redirect: false,
182            x11_window_types: vec![WindowType::Normal],
183            embed_window: None,
184        }
185    }
186}
187
188impl WindowAttributesX11 {
189    /// Create this window with a specific X11 visual.
190    pub fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
191        self.visual_id = Some(visual_id);
192        self
193    }
194
195    pub fn with_x11_screen(mut self, screen_id: i32) -> Self {
196        self.screen_id = Some(screen_id);
197        self
198    }
199
200    /// Build window with the given `general` and `instance` names.
201    ///
202    /// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
203    /// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance",
204    /// "general"`.
205    ///
206    /// For details about application ID conventions, see the
207    /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
208    pub fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
209        self.name = Some(ApplicationName { general: general.into(), instance: instance.into() });
210        self
211    }
212
213    /// Build window with override-redirect flag; defaults to false.
214    pub fn with_override_redirect(mut self, override_redirect: bool) -> Self {
215        self.override_redirect = override_redirect;
216        self
217    }
218
219    /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`.
220    pub fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
221        self.x11_window_types = x11_window_types;
222        self
223    }
224
225    /// Build window with base size hint.
226    ///
227    /// ```
228    /// # use wingy::dpi::{LogicalSize, PhysicalSize};
229    /// # use wingy::window::{Window, WindowAttributes};
230    /// # use wingy::platform::x11::WindowAttributesX11;
231    /// // Specify the size in logical dimensions like this:
232    /// WindowAttributesX11::default().with_base_size(LogicalSize::new(400.0, 200.0));
233    ///
234    /// // Or specify the size in physical dimensions like this:
235    /// WindowAttributesX11::default().with_base_size(PhysicalSize::new(400, 200));
236    /// ```
237    pub fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
238        self.base_size = Some(base_size.into());
239        self
240    }
241
242    /// Embed this window into another parent window.
243    ///
244    /// # Example
245    ///
246    /// ```no_run
247    /// use wingy::window::{Window, WindowAttributes};
248    /// use wingy::event_loop::ActiveEventLoop;
249    /// use wingy::platform::x11::{XWindow, WindowAttributesX11};
250    /// # fn create_window(event_loop: &dyn ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
251    /// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
252    /// let window_x11_attributes = WindowAttributesX11::default().with_embed_parent_window(parent_window_id);
253    /// let window_attributes = WindowAttributes::default().with_platform_attributes(Box::new(window_x11_attributes));
254    /// let window = event_loop.create_window(window_attributes)?;
255    /// # Ok(()) }
256    /// ```
257    pub fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
258        self.embed_window = Some(parent_window_id);
259        self
260    }
261
262    #[inline]
263    pub fn with_activation_token(mut self, token: ActivationToken) -> Self {
264        self.activation_token = Some(token);
265        self
266    }
267}
268
269impl PlatformWindowAttributes for WindowAttributesX11 {
270    fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
271        Box::from(self.clone())
272    }
273}