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}