Skip to main content

miracle_plugin/
window.rs

1use super::application::*;
2use super::bindings;
3use super::container::*;
4use super::core::{self, *};
5use super::host::*;
6use super::workspace::*;
7use glam::Mat4;
8
9#[doc(hidden)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u32)]
12pub enum WindowAttrib {
13    Type = 0,
14    State = 1,
15    Focus = 3,
16    Dpi = 4,
17    Visibility = 5,
18    PreferredOrientation = 6,
19}
20
21impl From<WindowAttrib> for bindings::MirWindowAttrib {
22    fn from(value: WindowAttrib) -> Self {
23        value as bindings::MirWindowAttrib
24    }
25}
26
27impl TryFrom<bindings::MirWindowAttrib> for WindowAttrib {
28    type Error = ();
29
30    fn try_from(value: bindings::MirWindowAttrib) -> Result<Self, Self::Error> {
31        match value {
32            0 => Ok(Self::Type),
33            1 => Ok(Self::State),
34            3 => Ok(Self::Focus),
35            4 => Ok(Self::Dpi),
36            5 => Ok(Self::Visibility),
37            6 => Ok(Self::PreferredOrientation),
38            _ => Err(()),
39        }
40    }
41}
42
43/// Window type.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
45#[repr(u32)]
46pub enum WindowType {
47    /// AKA "regular"
48    #[default]
49    Normal = 0,
50    /// AKA "floating"
51    Utility = 1,
52    /// A dialog box.
53    Dialog = 2,
54    /// A splash or branding surface.
55    Gloss = 3,
56    /// A window whose layout is entirely managed by a plugin.
57    Freestyle = 4,
58    /// A popup or context menu.
59    Menu = 5,
60    /// AKA "OSK" or handwriting etc.
61    InputMethod = 6,
62    /// AKA "toolbox"/"toolbar"
63    Satellite = 7,
64    /// AKA "tooltip"
65    Tip = 8,
66    /// A server-side window decoration surface.
67    Decoration = 9,
68}
69
70impl From<WindowType> for bindings::MirWindowType {
71    fn from(value: WindowType) -> Self {
72        value as bindings::MirWindowType
73    }
74}
75
76impl TryFrom<bindings::MirWindowType> for WindowType {
77    type Error = ();
78
79    fn try_from(value: bindings::MirWindowType) -> Result<Self, Self::Error> {
80        match value {
81            0 => Ok(Self::Normal),
82            1 => Ok(Self::Utility),
83            2 => Ok(Self::Dialog),
84            3 => Ok(Self::Gloss),
85            4 => Ok(Self::Freestyle),
86            5 => Ok(Self::Menu),
87            6 => Ok(Self::InputMethod),
88            7 => Ok(Self::Satellite),
89            8 => Ok(Self::Tip),
90            9 => Ok(Self::Decoration),
91            _ => Err(()),
92        }
93    }
94}
95
96/// Window state.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98#[repr(u32)]
99pub enum WindowState {
100    #[default]
101    Unknown = 0,
102    Restored = 1,
103    Minimized = 2,
104    Maximized = 3,
105    VertMaximized = 4,
106    Fullscreen = 5,
107    HorizMaximized = 6,
108    Hidden = 7,
109    /// Used for panels, notifications and other windows attached to output edges.
110    Attached = 8,
111}
112
113impl From<WindowState> for bindings::MirWindowState {
114    fn from(value: WindowState) -> Self {
115        value as bindings::MirWindowState
116    }
117}
118
119impl TryFrom<bindings::MirWindowState> for WindowState {
120    type Error = ();
121
122    fn try_from(value: bindings::MirWindowState) -> Result<Self, Self::Error> {
123        match value {
124            0 => Ok(Self::Unknown),
125            1 => Ok(Self::Restored),
126            2 => Ok(Self::Minimized),
127            3 => Ok(Self::Maximized),
128            4 => Ok(Self::VertMaximized),
129            5 => Ok(Self::Fullscreen),
130            6 => Ok(Self::HorizMaximized),
131            7 => Ok(Self::Hidden),
132            8 => Ok(Self::Attached),
133            _ => Err(()),
134        }
135    }
136}
137
138/// Window focus state.
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
140#[repr(u32)]
141pub enum WindowFocusState {
142    /// Inactive and does not have focus.
143    #[default]
144    Unfocused = 0,
145    /// Active and has keyboard focus.
146    Focused = 1,
147    /// Active but does not have keyboard focus.
148    Active = 2,
149}
150
151impl From<WindowFocusState> for bindings::MirWindowFocusState {
152    fn from(value: WindowFocusState) -> Self {
153        value as bindings::MirWindowFocusState
154    }
155}
156
157impl TryFrom<bindings::MirWindowFocusState> for WindowFocusState {
158    type Error = ();
159
160    fn try_from(value: bindings::MirWindowFocusState) -> Result<Self, Self::Error> {
161        match value {
162            0 => Ok(Self::Unfocused),
163            1 => Ok(Self::Focused),
164            2 => Ok(Self::Active),
165            _ => Err(()),
166        }
167    }
168}
169
170/// Window visibility state.
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
172#[repr(u32)]
173pub enum WindowVisibility {
174    /// The window is fully obscured by other surfaces.
175    #[default]
176    Occluded = 0,
177    /// The window is at least partially visible.
178    Exposed = 1,
179}
180
181impl From<WindowVisibility> for bindings::MirWindowVisibility {
182    fn from(value: WindowVisibility) -> Self {
183        value as bindings::MirWindowVisibility
184    }
185}
186
187impl TryFrom<bindings::MirWindowVisibility> for WindowVisibility {
188    type Error = ();
189
190    fn try_from(value: bindings::MirWindowVisibility) -> Result<Self, Self::Error> {
191        match value {
192            0 => Ok(Self::Occluded),
193            1 => Ok(Self::Exposed),
194            _ => Err(()),
195        }
196    }
197}
198
199/// Controls the z-ordering layer of a freestyle window.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
201#[repr(u32)]
202pub enum DepthLayer {
203    /// For desktop backgrounds (lowest layer).
204    Background = 0,
205    /// For panels or other controls/decorations below normal windows.
206    Below = 1,
207    /// For normal application windows.
208    #[default]
209    Application = 2,
210    /// For always-on-top application windows.
211    AlwaysOnTop = 3,
212    /// For panels or notifications that want to be above normal windows.
213    Above = 4,
214    /// For overlays such as lock screens (highest layer).
215    Overlay = 5,
216}
217
218impl From<DepthLayer> for bindings::MirDepthLayer {
219    fn from(value: DepthLayer) -> Self {
220        value as bindings::MirDepthLayer
221    }
222}
223
224impl TryFrom<bindings::MirDepthLayer> for DepthLayer {
225    type Error = ();
226
227    fn try_from(value: bindings::MirDepthLayer) -> Result<Self, Self::Error> {
228        match value {
229            0 => Ok(Self::Background),
230            1 => Ok(Self::Below),
231            2 => Ok(Self::Application),
232            3 => Ok(Self::AlwaysOnTop),
233            4 => Ok(Self::Above),
234            5 => Ok(Self::Overlay),
235            _ => Err(()),
236        }
237    }
238}
239
240/// A snapshot of a window's state at the time of a plugin callback.
241///
242/// `WindowInfo` is read-only. To mutate a window managed by your plugin,
243/// use [`PluginWindow`] (returned by [`crate::plugin::managed_windows`]).
244#[derive(Debug)]
245pub struct WindowInfo {
246    /// The type of this window.
247    pub window_type: WindowType,
248    /// The state of the window.
249    pub state: WindowState,
250    /// The position of the window.
251    pub top_left: Point,
252    /// The size of the window.
253    pub size: Size,
254    /// The depth layer of the window.
255    pub depth_layer: DepthLayer,
256    /// The name of the window.
257    pub name: String,
258    /// The 4x4 transform matrix of the window (column-major).
259    pub transform: Mat4,
260    /// The alpha (opacity) of the window.
261    pub alpha: f32,
262    /// Internal pointer for C interop.
263    internal: u64,
264}
265
266impl WindowInfo {
267    #[doc(hidden)]
268    pub unsafe fn from_c_with_name(value: &bindings::miracle_window_info_t, name: String) -> Self {
269        Self {
270            window_type: WindowType::try_from(value.window_type).unwrap_or_default(),
271            state: WindowState::try_from(value.state).unwrap_or_default(),
272            top_left: value.top_left.into(),
273            size: value.size.into(),
274            depth_layer: DepthLayer::try_from(value.depth_layer).unwrap_or_default(),
275            name,
276            transform: core::mat4_from_f32_array(value.transform),
277            alpha: value.alpha,
278            internal: value.internal,
279        }
280    }
281
282    /// Retrieve the ID of this window.
283    ///
284    /// Plugins may elect to keep a reference to this ID so that they can
285    /// match it with [`WindowInfo`] later.
286    pub fn id(&self) -> u64 {
287        self.internal
288    }
289
290    /// Get the application that owns this window.
291    pub fn application(&self) -> Option<ApplicationInfo> {
292        const NAME_BUF_LEN: usize = 256;
293        let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
294
295        unsafe {
296            let internal = miracle_window_info_get_application(
297                self.internal as i64,
298                name_buf.as_mut_ptr() as i32,
299                NAME_BUF_LEN as i32,
300            );
301
302            if internal == -1 {
303                return None;
304            }
305
306            // Find the null terminator to get the actual string length
307            let name_len = name_buf
308                .iter()
309                .position(|&c| c == 0)
310                .unwrap_or(NAME_BUF_LEN);
311            let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
312
313            Some(ApplicationInfo {
314                name,
315                internal: internal as u64,
316            })
317        }
318    }
319
320    /// Get the container that holds this window.
321    pub fn container(&self) -> Option<Container> {
322        let mut container = std::mem::MaybeUninit::<crate::bindings::miracle_container_t>::uninit();
323
324        unsafe {
325            let result = miracle_window_info_get_container(
326                self.internal as i64,
327                container.as_mut_ptr() as i32,
328            );
329
330            if result != 0 {
331                return None;
332            }
333
334            Some(Container::from(container.assume_init()))
335        }
336    }
337
338    /// Get the workspace that this window is on.
339    pub fn workspace(&self) -> Option<Workspace> {
340        const NAME_BUF_LEN: usize = 256;
341        let mut workspace = std::mem::MaybeUninit::<crate::bindings::miracle_workspace_t>::uninit();
342        let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
343
344        unsafe {
345            let result = miracle_window_info_get_workspace(
346                self.internal as i64,
347                workspace.as_mut_ptr() as i32,
348                name_buf.as_mut_ptr() as i32,
349                NAME_BUF_LEN as i32,
350            );
351
352            if result != 0 {
353                return None;
354            }
355
356            let workspace = workspace.assume_init();
357            if workspace.is_set == 0 {
358                return None;
359            }
360
361            // Find the null terminator to get the actual string length
362            let name_len = name_buf
363                .iter()
364                .position(|&c| c == 0)
365                .unwrap_or(NAME_BUF_LEN);
366            let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
367
368            Some(Workspace::from_c_with_name(&workspace, name))
369        }
370    }
371}
372
373impl PartialEq for WindowInfo {
374    fn eq(&self, other: &Self) -> bool {
375        self.internal == other.internal
376    }
377}
378
379/// A handle to a window managed by this plugin, with mutation methods.
380///
381/// Returned by [`crate::plugin::Plugin::managed_windows`]. Wraps [`WindowInfo`] and exposes
382/// all of its read-only fields via [`std::ops::Deref`], while adding setter methods that
383/// call into the compositor host.
384#[derive(Debug)]
385pub struct PluginWindow {
386    info: WindowInfo,
387}
388
389impl PluginWindow {
390    #[doc(hidden)]
391    pub fn from_window_info(info: WindowInfo) -> Self {
392        Self { info }
393    }
394
395    /// Set the state of this window.
396    pub fn set_state(&self, state: WindowState) -> Result<(), ()> {
397        let r = unsafe { miracle_window_set_state(self.info.internal as i64, state as i32) };
398        if r == 0 { Ok(()) } else { Err(()) }
399    }
400
401    /// Move this window to a different workspace.
402    pub fn set_workspace(&self, workspace: &Workspace) -> Result<(), ()> {
403        let r = unsafe {
404            miracle_window_set_workspace(self.info.internal as i64, workspace.id() as i64)
405        };
406        if r == 0 { Ok(()) } else { Err(()) }
407    }
408
409    /// Set the position and size of this window.
410    pub fn set_rectangle(&self, rect: Rectangle, animate: bool) -> Result<(), ()> {
411        let r = unsafe {
412            miracle_window_set_rectangle(
413                self.info.internal as i64,
414                rect.x,
415                rect.y,
416                rect.width,
417                rect.height,
418                if animate { 1 } else { 0 },
419            )
420        };
421        if r == 0 { Ok(()) } else { Err(()) }
422    }
423
424    /// Set the 4x4 column-major transform matrix of this window.
425    pub fn set_transform(&self, transform: Mat4) -> Result<(), ()> {
426        let arr = transform.to_cols_array();
427        let r =
428            unsafe { miracle_window_set_transform(self.info.internal as i64, arr.as_ptr() as i32) };
429        if r == 0 { Ok(()) } else { Err(()) }
430    }
431
432    /// Set the alpha (opacity) of this window.
433    pub fn set_alpha(&self, alpha: f32) -> Result<(), ()> {
434        let r = unsafe {
435            miracle_window_set_alpha(self.info.internal as i64, (&alpha as *const f32) as i32)
436        };
437        if r == 0 { Ok(()) } else { Err(()) }
438    }
439
440    /// Request keyboard focus on this window.
441    pub fn request_focus(&self) -> Result<(), ()> {
442        let r = unsafe { miracle_window_request_focus(self.info.internal as i64) };
443        if r == 0 { Ok(()) } else { Err(()) }
444    }
445}
446
447impl std::ops::Deref for PluginWindow {
448    type Target = WindowInfo;
449
450    fn deref(&self) -> &Self::Target {
451        &self.info
452    }
453}
454
455impl PartialEq for PluginWindow {
456    fn eq(&self, other: &Self) -> bool {
457        self.info == other.info
458    }
459}