Skip to main content

miracle_plugin/
window.rs

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