pinnacle_api/
window.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Window management.
6//!
7//! This module provides ways to get [`WindowHandle`]s and move and resize
8//! windows using the mouse.
9//!
10//! [`WindowHandle`]s allow you to do things like resize and move windows, toggle them between
11//! floating and tiled, close them, and more.
12
13use std::borrow::Borrow;
14
15use futures::FutureExt;
16use pinnacle_api_defs::pinnacle::{
17    util::v1::SetOrToggle,
18    window::{
19        self,
20        v1::{
21            GetAppIdRequest, GetFocusedRequest, GetForeignToplevelListIdentifierRequest,
22            GetLayoutModeRequest, GetLocRequest, GetSizeRequest, GetTagIdsRequest, GetTitleRequest,
23            GetWindowsInDirRequest, LowerRequest, MoveGrabRequest, MoveToOutputRequest,
24            MoveToTagRequest, RaiseRequest, ResizeGrabRequest, ResizeTileRequest,
25            SetDecorationModeRequest, SetFloatingRequest, SetFocusedRequest, SetFullscreenRequest,
26            SetGeometryRequest, SetMaximizedRequest, SetTagRequest, SetTagsRequest,
27            SetVrrDemandRequest, SwapRequest,
28        },
29    },
30};
31use tokio::sync::mpsc::unbounded_channel;
32use tokio_stream::StreamExt;
33
34use crate::{
35    BlockOnTokio,
36    client::Client,
37    input::MouseButton,
38    output::OutputHandle,
39    signal::{SignalHandle, WindowSignal},
40    tag::TagHandle,
41    util::{Batch, Direction, Point, Size},
42};
43
44/// Gets handles to all windows.
45///
46/// # Examples
47///
48/// ```no_run
49/// # use pinnacle_api::window;
50/// for win in window::get_all() {
51///     println!("{}", win.title());
52/// }
53/// ```
54pub fn get_all() -> impl Iterator<Item = WindowHandle> {
55    get_all_async().block_on_tokio()
56}
57
58/// Async impl for [`get_all`].
59pub async fn get_all_async() -> impl Iterator<Item = WindowHandle> {
60    let window_ids = Client::window()
61        .get(pinnacle_api_defs::pinnacle::window::v1::GetRequest {})
62        .await
63        .unwrap()
64        .into_inner()
65        .window_ids;
66
67    window_ids.into_iter().map(|id| WindowHandle { id })
68}
69
70/// Gets a handle to the window with the current keyboard focus.
71///
72/// # Examples
73///
74/// ```no_run
75/// # use pinnacle_api::window;
76/// if let Some(focused) = window::get_focused() {
77///     println!("{}", focused.title());
78/// }
79/// ```
80pub fn get_focused() -> Option<WindowHandle> {
81    get_focused_async().block_on_tokio()
82}
83
84/// Async impl for [`get_focused`].
85pub async fn get_focused_async() -> Option<WindowHandle> {
86    let windows = get_all_async().await;
87
88    windows.batch_find(|win| win.focused_async().boxed(), |focused| *focused)
89}
90
91/// Begins an interactive window move.
92///
93/// This will start moving the window under the pointer until `button` is released.
94///
95/// `button` should be the mouse button that is held at the time
96/// this function is called. Otherwise, the move will not start.
97/// This is intended for use in tandem with a mousebind.
98///
99/// # Examples
100///
101/// ```no_run
102/// # use pinnacle_api::window;
103/// # use pinnacle_api::input;
104/// # use pinnacle_api::input::Mod;
105/// # use pinnacle_api::input::MouseButton;
106/// input::mousebind(Mod::SUPER, MouseButton::Left)
107///     .on_press(|| window::begin_move(MouseButton::Left));
108/// ```
109pub fn begin_move(button: MouseButton) {
110    Client::window()
111        .move_grab(MoveGrabRequest {
112            button: button.into(),
113        })
114        .block_on_tokio()
115        .unwrap();
116}
117
118/// Begins an interactive window resize.
119///
120/// This will start resizing the window under the pointer until `button` is released.
121///
122/// `button` should be the mouse button that is held at the time
123/// this function is called. Otherwise, the move will not start.
124/// This is intended for use in tandem with a mousebind.
125///
126/// # Examples
127///
128/// ```no_run
129/// # use pinnacle_api::window;
130/// # use pinnacle_api::input;
131/// # use pinnacle_api::input::Mod;
132/// # use pinnacle_api::input::MouseButton;
133/// input::mousebind(Mod::SUPER, MouseButton::Right)
134///     .on_press(|| window::begin_resize(MouseButton::Right));
135/// ```
136pub fn begin_resize(button: MouseButton) {
137    Client::window()
138        .resize_grab(ResizeGrabRequest {
139            button: button.into(),
140        })
141        .block_on_tokio()
142        .unwrap();
143}
144
145/// Connects to a [`WindowSignal`].
146///
147/// # Examples
148///
149/// ```no_run
150/// # use pinnacle_api::window;
151/// # use pinnacle_api::signal::WindowSignal;
152/// window::connect_signal(WindowSignal::PointerEnter(Box::new(|window| {
153///     window.set_focused(true);
154/// })));
155/// ```
156pub fn connect_signal(signal: WindowSignal) -> SignalHandle {
157    let mut signal_state = Client::signal_state();
158
159    match signal {
160        WindowSignal::PointerEnter(f) => signal_state.window_pointer_enter.add_callback(f),
161        WindowSignal::PointerLeave(f) => signal_state.window_pointer_leave.add_callback(f),
162        WindowSignal::Focused(f) => signal_state.window_focused.add_callback(f),
163        WindowSignal::TitleChanged(f) => signal_state.window_title_changed.add_callback(f),
164        WindowSignal::LayoutModeChanged(f) => {
165            signal_state.window_layout_mode_changed.add_callback(f)
166        }
167        WindowSignal::Created(f) => signal_state.window_created.add_callback(f),
168        WindowSignal::Destroyed(f) => signal_state.window_destroyed.add_callback(f),
169    }
170}
171
172/// A handle to a window.
173///
174/// This allows you to manipulate the window and get its properties.
175#[derive(Debug, Clone, PartialEq, Eq, Hash)]
176pub struct WindowHandle {
177    pub(crate) id: u32,
178}
179
180/// A window's current layout mode.
181#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
182pub enum LayoutMode {
183    /// The window is tiled.
184    Tiled,
185    /// The window is floating.
186    Floating,
187    /// The window is fullscreen.
188    Fullscreen,
189    /// The window is maximized.
190    Maximized,
191}
192
193impl TryFrom<window::v1::LayoutMode> for LayoutMode {
194    type Error = ();
195
196    fn try_from(value: window::v1::LayoutMode) -> Result<Self, Self::Error> {
197        match value {
198            window::v1::LayoutMode::Unspecified => Err(()),
199            window::v1::LayoutMode::Tiled => Ok(LayoutMode::Tiled),
200            window::v1::LayoutMode::Floating => Ok(LayoutMode::Floating),
201            window::v1::LayoutMode::Fullscreen => Ok(LayoutMode::Fullscreen),
202            window::v1::LayoutMode::Maximized => Ok(LayoutMode::Maximized),
203            // window::v1::LayoutMode::Spilled => Ok(LayoutMode::Floating),
204        }
205    }
206}
207
208/// A mode for window decorations (titlebar, shadows, etc).
209#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
210pub enum DecorationMode {
211    /// The client should draw its own decorations.
212    ClientSide,
213    /// The server should draw decorations.
214    ServerSide,
215}
216
217/// A demand for variable refresh rate on an output.
218#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)]
219#[non_exhaustive]
220pub struct VrrDemand {
221    /// Whether the window must be fullscreen for vrr to turn on.
222    pub fullscreen: bool,
223}
224
225impl VrrDemand {
226    /// Creates a [`VrrDemand`] that turns on vrr when a window is visible.
227    pub fn when_visible() -> Self {
228        Default::default()
229    }
230
231    /// Creates a [`VrrDemand`] that turns on vrr when a window is both
232    /// visible *and* fullscreen.
233    pub fn when_fullscreen() -> Self {
234        Self { fullscreen: true }
235    }
236}
237
238impl WindowHandle {
239    /// Sends a close request to this window.
240    ///
241    /// If the window is unresponsive, it may not close.
242    pub fn close(&self) {
243        let window_id = self.id;
244        Client::window()
245            .close(pinnacle_api_defs::pinnacle::window::v1::CloseRequest { window_id })
246            .block_on_tokio()
247            .unwrap();
248    }
249
250    /// Sets this window's location and/or size.
251    ///
252    /// Only affects the floating geometry of windows. Tiled geometries are calculated
253    /// using the current layout.
254    pub fn set_geometry(
255        &self,
256        x: impl Into<Option<i32>>,
257        y: impl Into<Option<i32>>,
258        w: impl Into<Option<u32>>,
259        h: impl Into<Option<u32>>,
260    ) {
261        Client::window()
262            .set_geometry(SetGeometryRequest {
263                window_id: self.id,
264                x: x.into(),
265                y: y.into(),
266                w: w.into(),
267                h: h.into(),
268            })
269            .block_on_tokio()
270            .unwrap();
271    }
272
273    /// If this window is tiled, resizes its tile by shifting the left, right,
274    /// top, and bottom edges by the provided pixel amounts.
275    ///
276    /// Positive amounts shift edges right/down, while negative amounts
277    /// shift edges left/up.
278    ///
279    /// If this resizes the tile in a direction that it can no longer resize
280    /// towards (e.g. it's at the edge of the screen), it will resize in the opposite
281    /// direction.
282    ///
283    /// # Examples
284    ///
285    /// ```no_run
286    /// # use pinnacle_api::window;
287    /// # || {
288    /// // Grow the focused tiled window 10 pixels leftward
289    /// window::get_focused()?.resize_tile(-10, 0, 0, 0);
290    ///
291    /// // Shrink the focused tiled window 10 pixels inward from the right
292    /// window::get_focused()?.resize_tile(0, -10, 0, 0);
293    ///
294    /// // Grow the focused tiled window 20 pixels centered vertically
295    /// window::get_focused()?.resize_tile(0, 0, -10, 10);
296    /// # Some(())
297    /// # };
298    /// ```
299    pub fn resize_tile(&self, left: i32, right: i32, top: i32, bottom: i32) {
300        Client::window()
301            .resize_tile(ResizeTileRequest {
302                window_id: self.id,
303                left,
304                right,
305                top,
306                bottom,
307            })
308            .block_on_tokio()
309            .unwrap();
310    }
311
312    /// Sets this window to fullscreen or not.
313    pub fn set_fullscreen(&self, set: bool) {
314        let window_id = self.id;
315        Client::window()
316            .set_fullscreen(SetFullscreenRequest {
317                window_id,
318                set_or_toggle: match set {
319                    true => SetOrToggle::Set,
320                    false => SetOrToggle::Unset,
321                }
322                .into(),
323            })
324            .block_on_tokio()
325            .unwrap();
326    }
327
328    /// Toggles this window between fullscreen and not.
329    pub fn toggle_fullscreen(&self) {
330        let window_id = self.id;
331        Client::window()
332            .set_fullscreen(SetFullscreenRequest {
333                window_id,
334                set_or_toggle: SetOrToggle::Toggle.into(),
335            })
336            .block_on_tokio()
337            .unwrap();
338    }
339
340    /// Sets this window to maximized or not.
341    pub fn set_maximized(&self, set: bool) {
342        let window_id = self.id;
343        Client::window()
344            .set_maximized(SetMaximizedRequest {
345                window_id,
346                set_or_toggle: match set {
347                    true => SetOrToggle::Set,
348                    false => SetOrToggle::Unset,
349                }
350                .into(),
351            })
352            .block_on_tokio()
353            .unwrap();
354    }
355
356    /// Toggles this window between maximized and not.
357    pub fn toggle_maximized(&self) {
358        let window_id = self.id;
359        Client::window()
360            .set_maximized(SetMaximizedRequest {
361                window_id,
362                set_or_toggle: SetOrToggle::Toggle.into(),
363            })
364            .block_on_tokio()
365            .unwrap();
366    }
367
368    /// Sets this window to floating or not.
369    ///
370    /// Floating windows will not be tiled and can be moved around and resized freely.
371    pub fn set_floating(&self, set: bool) {
372        let window_id = self.id;
373        Client::window()
374            .set_floating(SetFloatingRequest {
375                window_id,
376                set_or_toggle: match set {
377                    true => SetOrToggle::Set,
378                    false => SetOrToggle::Unset,
379                }
380                .into(),
381            })
382            .block_on_tokio()
383            .unwrap();
384    }
385
386    /// Toggles this window to and from floating.
387    ///
388    /// Floating windows will not be tiled and can be moved around and resized freely.
389    pub fn toggle_floating(&self) {
390        let window_id = self.id;
391        Client::window()
392            .set_floating(SetFloatingRequest {
393                window_id,
394                set_or_toggle: SetOrToggle::Toggle.into(),
395            })
396            .block_on_tokio()
397            .unwrap();
398    }
399
400    /// Focuses or unfocuses this window.
401    pub fn set_focused(&self, set: bool) {
402        let window_id = self.id;
403        Client::window()
404            .set_focused(SetFocusedRequest {
405                window_id,
406                set_or_toggle: match set {
407                    true => SetOrToggle::Set,
408                    false => SetOrToggle::Unset,
409                }
410                .into(),
411            })
412            .block_on_tokio()
413            .unwrap();
414    }
415
416    /// Toggles this window between focused and unfocused.
417    pub fn toggle_focused(&self) {
418        let window_id = self.id;
419        Client::window()
420            .set_focused(SetFocusedRequest {
421                window_id,
422                set_or_toggle: SetOrToggle::Toggle.into(),
423            })
424            .block_on_tokio()
425            .unwrap();
426    }
427
428    /// Sets this window's decoration mode.
429    pub fn set_decoration_mode(&self, mode: DecorationMode) {
430        Client::window()
431            .set_decoration_mode(SetDecorationModeRequest {
432                window_id: self.id,
433                decoration_mode: match mode {
434                    DecorationMode::ClientSide => window::v1::DecorationMode::ClientSide,
435                    DecorationMode::ServerSide => window::v1::DecorationMode::ServerSide,
436                }
437                .into(),
438            })
439            .block_on_tokio()
440            .unwrap();
441    }
442
443    /// Moves this window to the specified output.
444    ///
445    /// This will set the window tags to the output tags, and update the window position.
446    ///
447    /// # Example
448    ///
449    /// ```no_run
450    /// # use pinnacle_api::window;
451    /// # use pinnacle_api::output;
452    /// # || {
453    /// // Move the focused window to output DP-2
454    /// window::get_focused()?.move_to_output(&output::get_by_name("DP-2")?);
455    /// # Some(())
456    /// # };
457    /// ```
458    pub fn move_to_output(&self, output: &OutputHandle) {
459        let window_id = self.id;
460        let output_name = output.name();
461
462        Client::window()
463            .move_to_output(MoveToOutputRequest {
464                window_id,
465                output_name,
466            })
467            .block_on_tokio()
468            .unwrap();
469    }
470
471    /// Moves this window to the given `tag`.
472    ///
473    /// This will remove all tags from this window then tag it with `tag`, essentially moving the
474    /// window to that tag.
475    ///
476    /// # Examples
477    ///
478    /// ```no_run
479    /// # use pinnacle_api::window;
480    /// # use pinnacle_api::tag;
481    /// # || {
482    /// // Move the focused window to tag "Code" on the focused output
483    /// window::get_focused()?.move_to_tag(&tag::get("Code")?);
484    /// # Some(())
485    /// # };
486    /// ```
487    pub fn move_to_tag(&self, tag: &TagHandle) {
488        let window_id = self.id;
489        let tag_id = tag.id;
490        Client::window()
491            .move_to_tag(MoveToTagRequest { window_id, tag_id })
492            .block_on_tokio()
493            .unwrap();
494    }
495
496    /// Sets or unsets a tag on this window.
497    ///
498    /// # Examples
499    ///
500    /// ```no_run
501    /// # use pinnacle_api::window;
502    /// # use pinnacle_api::tag;
503    /// # || {
504    /// let focused = window::get_focused()?;
505    /// let tag = tag::get("Potato")?;
506    ///
507    /// focused.set_tag(&tag, true); // `focused` now has tag "Potato"
508    /// focused.set_tag(&tag, false); // `focused` no longer has tag "Potato"
509    /// # Some(())
510    /// # };
511    /// ```
512    pub fn set_tag(&self, tag: &TagHandle, set: bool) {
513        let window_id = self.id;
514        let tag_id = tag.id;
515        Client::window()
516            .set_tag(SetTagRequest {
517                window_id,
518                tag_id,
519                set_or_toggle: match set {
520                    true => SetOrToggle::Set,
521                    false => SetOrToggle::Unset,
522                }
523                .into(),
524            })
525            .block_on_tokio()
526            .unwrap();
527    }
528
529    /// Toggles a tag on this window.
530    ///
531    /// # Examples
532    ///
533    /// ```no_run
534    /// # use pinnacle_api::window;
535    /// # use pinnacle_api::tag;
536    /// # || {
537    /// let focused = window::get_focused()?;
538    /// let tag = tag::get("Potato")?;
539    ///
540    /// focused.toggle_tag(&tag); // `focused` now has tag "Potato"
541    /// focused.toggle_tag(&tag); // `focused` no longer has tag "Potato"
542    /// # Some(())
543    /// # };
544    /// ```
545    pub fn toggle_tag(&self, tag: &TagHandle) {
546        let window_id = self.id;
547        let tag_id = tag.id;
548        Client::window()
549            .set_tag(SetTagRequest {
550                window_id,
551                tag_id,
552                set_or_toggle: SetOrToggle::Toggle.into(),
553            })
554            .block_on_tokio()
555            .unwrap();
556    }
557
558    /// Sets the exact provided tags on this window.
559    ///
560    /// Passing in an empty collection will not change the window's tags.
561    ///
562    /// For ergonomics, this accepts iterators of both `TagHandle` and `&TagHandle`.
563    ///
564    /// # Examples
565    ///
566    /// ```no_run
567    /// # use pinnacle_api::window;
568    /// # use pinnacle_api::tag;
569    /// # || {
570    /// let focused = window::get_focused()?;
571    /// let tag1 = tag::get("1")?;
572    /// let tag3 = tag::get("3")?;
573    ///
574    /// // Set `focused`'s tags to "1" and "3", removing all others
575    /// focused.set_tags([tag1, tag3]);
576    /// # Some(())
577    /// # };
578    /// ```
579    pub fn set_tags<T: Borrow<TagHandle>>(&self, tags: impl IntoIterator<Item = T>) {
580        let window_id = self.id;
581        let tag_ids = tags
582            .into_iter()
583            .map(|tag| {
584                let tag: &TagHandle = tag.borrow();
585                tag.id
586            })
587            .collect::<Vec<_>>();
588
589        Client::window()
590            .set_tags(SetTagsRequest { window_id, tag_ids })
591            .block_on_tokio()
592            .unwrap();
593    }
594
595    /// Sets this window's [`VrrDemand`].
596    ///
597    /// When set to `None`, this window has no vrr demand.
598    ///
599    /// This works in conjunction with an output with
600    /// [`Vrr::OnDemand`](crate::output::Vrr::OnDemand).
601    pub fn set_vrr_demand(&self, vrr_demand: impl Into<Option<VrrDemand>>) {
602        let window_id = self.id;
603        let vrr_demand: Option<_> = vrr_demand.into();
604
605        Client::window()
606            .set_vrr_demand(SetVrrDemandRequest {
607                window_id,
608                vrr_demand: vrr_demand.map(|vrr_demand| window::v1::VrrDemand {
609                    fullscreen: vrr_demand.fullscreen,
610                }),
611            })
612            .block_on_tokio()
613            .unwrap();
614    }
615
616    /// Raises this window to the front.
617    pub fn raise(&self) {
618        let window_id = self.id;
619        Client::window()
620            .raise(RaiseRequest { window_id })
621            .block_on_tokio()
622            .unwrap();
623    }
624
625    /// Lowers this window to the back.
626    pub fn lower(&self) {
627        let window_id = self.id;
628        Client::window()
629            .lower(LowerRequest { window_id })
630            .block_on_tokio()
631            .unwrap();
632    }
633
634    /// Gets this window's current location in the global space.
635    pub fn loc(&self) -> Option<Point> {
636        self.loc_async().block_on_tokio()
637    }
638
639    /// Async impl for [`Self::loc`].
640    pub async fn loc_async(&self) -> Option<Point> {
641        let window_id = self.id;
642        Client::window()
643            .get_loc(GetLocRequest { window_id })
644            .await
645            .unwrap()
646            .into_inner()
647            .loc
648            .map(|loc| Point { x: loc.x, y: loc.y })
649    }
650
651    /// Gets this window's current size.
652    pub fn size(&self) -> Option<Size> {
653        self.size_async().block_on_tokio()
654    }
655
656    /// Async impl for [`Self::size`].
657    pub async fn size_async(&self) -> Option<Size> {
658        let window_id = self.id;
659        Client::window()
660            .get_size(GetSizeRequest { window_id })
661            .await
662            .unwrap()
663            .into_inner()
664            .size
665            .map(|size| Size {
666                w: size.width,
667                h: size.height,
668            })
669    }
670
671    /// Gets this window's app id (class if it's an xwayland window).
672    ///
673    /// If it doesn't have one, this returns an empty string.
674    pub fn app_id(&self) -> String {
675        self.app_id_async().block_on_tokio()
676    }
677
678    /// Async impl for [`Self::app_id`].
679    pub async fn app_id_async(&self) -> String {
680        let window_id = self.id;
681        Client::window()
682            .get_app_id(GetAppIdRequest { window_id })
683            .await
684            .unwrap()
685            .into_inner()
686            .app_id
687    }
688
689    /// Gets this window's title.
690    ///
691    /// If it doesn't have one, this returns an empty string.
692    pub fn title(&self) -> String {
693        self.title_async().block_on_tokio()
694    }
695
696    /// Async impl for [`Self::title`].
697    pub async fn title_async(&self) -> String {
698        let window_id = self.id;
699        Client::window()
700            .get_title(GetTitleRequest { window_id })
701            .await
702            .unwrap()
703            .into_inner()
704            .title
705    }
706
707    /// Gets this window's output.
708    ///
709    /// This is currently implemented as the output of the first
710    /// tag that this window has.
711    ///
712    /// Returns `None` if this window doesn't exist or if it has no tags.
713    pub fn output(&self) -> Option<OutputHandle> {
714        self.output_async().block_on_tokio()
715    }
716
717    /// Async impl for [`Self::output`].
718    pub async fn output_async(&self) -> Option<OutputHandle> {
719        Some(self.tags_async().await.next()?.output())
720    }
721
722    /// Gets whether or not this window has keyboard focus.
723    pub fn focused(&self) -> bool {
724        self.focused_async().block_on_tokio()
725    }
726
727    /// Async impl for [`Self::focused`].
728    pub async fn focused_async(&self) -> bool {
729        let window_id = self.id;
730        Client::window()
731            .get_focused(GetFocusedRequest { window_id })
732            .await
733            .unwrap()
734            .into_inner()
735            .focused
736    }
737
738    /// Gets this window's current [`LayoutMode`].
739    pub fn layout_mode(&self) -> LayoutMode {
740        self.layout_mode_async().block_on_tokio()
741    }
742
743    /// Async impl for [`Self::layout_mode`].
744    pub async fn layout_mode_async(&self) -> LayoutMode {
745        let window_id = self.id;
746        Client::window()
747            .get_layout_mode(GetLayoutModeRequest { window_id })
748            .await
749            .unwrap()
750            .into_inner()
751            .layout_mode()
752            .try_into()
753            .unwrap_or(LayoutMode::Tiled)
754    }
755
756    /// Gets whether or not this window is floating.
757    pub fn floating(&self) -> bool {
758        self.floating_async().block_on_tokio()
759    }
760
761    /// Async impl for [`Self::floating`].
762    pub async fn floating_async(&self) -> bool {
763        self.layout_mode_async().await == LayoutMode::Floating
764    }
765
766    /// Gets whether or not this window is tiled.
767    pub fn tiled(&self) -> bool {
768        self.tiled_async().block_on_tokio()
769    }
770
771    /// Async impl for [`Self::tiled`].
772    pub async fn tiled_async(&self) -> bool {
773        self.layout_mode_async().await == LayoutMode::Tiled
774    }
775
776    // /// Gets whether or not this window is spilled from the layout.
777    // ///
778    // /// A window is spilled when the current layout doesn't contains enough nodes and the
779    // /// compositor cannot assign a geometry to it. In that state, the window behaves as a floating
780    // /// window except that it gets tiled again if the number of nodes become big enough.
781    // pub fn spilled(&self) -> bool {
782    //     self.spilled_async().block_on_tokio()
783    // }
784
785    // /// Async impl for [`Self::spilled`].
786    // pub async fn spilled_async(&self) -> bool {
787    //     self.layout_mode_async().await == LayoutMode::Spilled
788    // }
789
790    /// Gets whether or not this window is fullscreen.
791    pub fn fullscreen(&self) -> bool {
792        self.fullscreen_async().block_on_tokio()
793    }
794
795    /// Async impl for [`Self::fullscreen`].
796    pub async fn fullscreen_async(&self) -> bool {
797        self.layout_mode_async().await == LayoutMode::Fullscreen
798    }
799
800    /// Gets whether or not this window is maximized.
801    pub fn maximized(&self) -> bool {
802        self.maximized_async().block_on_tokio()
803    }
804
805    /// Async impl for [`Self::maximized`].
806    pub async fn maximized_async(&self) -> bool {
807        self.layout_mode_async().await == LayoutMode::Maximized
808    }
809
810    /// Gets handles to all tags on this window.
811    pub fn tags(&self) -> impl Iterator<Item = TagHandle> + use<> {
812        self.tags_async().block_on_tokio()
813    }
814
815    /// Async impl for [`Self::tags`].
816    pub async fn tags_async(&self) -> impl Iterator<Item = TagHandle> + use<> {
817        let window_id = self.id;
818        Client::window()
819            .get_tag_ids(GetTagIdsRequest { window_id })
820            .await
821            .unwrap()
822            .into_inner()
823            .tag_ids
824            .into_iter()
825            .map(|id| TagHandle { id })
826    }
827
828    /// Gets whether or not this window has an active tag.
829    pub fn is_on_active_tag(&self) -> bool {
830        self.is_on_active_tag_async().block_on_tokio()
831    }
832
833    /// Async impl for [`Self::is_on_active_tag`].
834    pub async fn is_on_active_tag_async(&self) -> bool {
835        self.tags_async()
836            .await
837            .batch_find(|tag| tag.active_async().boxed(), |active| *active)
838            .is_some()
839    }
840
841    /// Gets all windows in the provided direction, sorted closest to farthest.
842    pub fn in_direction(&self, direction: Direction) -> impl Iterator<Item = WindowHandle> + use<> {
843        self.in_direction_async(direction).block_on_tokio()
844    }
845
846    /// Async impl for [`Self::in_direction`].
847    pub async fn in_direction_async(
848        &self,
849        direction: Direction,
850    ) -> impl Iterator<Item = WindowHandle> + use<> {
851        let window_id = self.id;
852
853        let mut request = GetWindowsInDirRequest {
854            window_id,
855            dir: Default::default(),
856        };
857
858        request.set_dir(match direction {
859            Direction::Left => pinnacle_api_defs::pinnacle::util::v1::Dir::Left,
860            Direction::Right => pinnacle_api_defs::pinnacle::util::v1::Dir::Right,
861            Direction::Up => pinnacle_api_defs::pinnacle::util::v1::Dir::Up,
862            Direction::Down => pinnacle_api_defs::pinnacle::util::v1::Dir::Down,
863        });
864
865        let response = Client::window()
866            .get_windows_in_dir(request)
867            .await
868            .unwrap()
869            .into_inner();
870
871        response.window_ids.into_iter().map(WindowHandle::from_id)
872    }
873
874    /// Gets this window's ext-foreign-toplevel-list handle identifier.
875    pub fn foreign_toplevel_list_identifier(&self) -> Option<String> {
876        self.foreign_toplevel_list_identifier_async()
877            .block_on_tokio()
878    }
879
880    /// Async impl for [`Self::foreign_toplevel_list_identifier`].
881    pub async fn foreign_toplevel_list_identifier_async(&self) -> Option<String> {
882        let window_id = self.id;
883
884        Client::window()
885            .get_foreign_toplevel_list_identifier(GetForeignToplevelListIdentifierRequest {
886                window_id,
887            })
888            .await
889            .unwrap()
890            .into_inner()
891            .identifier
892    }
893
894    /// Swap position with another window.
895    pub fn swap(&self, target: &WindowHandle) {
896        self.swap_async(target).block_on_tokio()
897    }
898
899    /// Async impl for [`Self::swap`].
900    pub async fn swap_async(&self, target: &WindowHandle) {
901        let request = SwapRequest {
902            window_id: self.id,
903            target_id: target.id,
904        };
905
906        Client::window().swap(request).await.unwrap();
907    }
908
909    /// Gets this window's raw compositor id.
910    pub fn id(&self) -> u32 {
911        self.id
912    }
913
914    /// Creates a window handle from an ID.
915    ///
916    /// Note: This is mostly for testing and if you want to serialize and deserialize window
917    /// handles.
918    pub fn from_id(id: u32) -> Self {
919        Self { id }
920    }
921}
922
923/// Adds a window rule.
924///
925/// Instead of using a declarative window rule system with match conditions,
926/// you supply a closure that acts on a newly opened window.
927/// You can use standard `if` statements and apply properties using the same
928/// methods that are used everywhere else in this API.
929///
930/// Note: this function is special in that if it is called, Pinnacle will wait for
931/// the provided closure to finish running before it sends windows an initial configure event.
932/// *Do not block here*. At best, short blocks will increase the time it takes for a window to
933/// open. At worst, a complete deadlock will prevent windows from opening at all.
934///
935/// # Examples
936///
937/// ```no_run
938/// # use pinnacle_api::window;
939/// # use pinnacle_api::window::DecorationMode;
940/// # use pinnacle_api::tag;
941/// window::add_window_rule(|window| {
942///     // Make Alacritty always open on the "Terminal" tag
943///     if window.app_id() == "Alacritty" {
944///         window.set_tag(&tag::get("Terminal").unwrap(), true);
945///     }
946///
947///     // Make all windows use client-side decorations
948///     window.set_decoration_mode(DecorationMode::ClientSide);
949/// });
950/// ```
951pub fn add_window_rule(mut for_all: impl FnMut(WindowHandle) + Send + 'static) {
952    let (client_outgoing, client_outgoing_to_server) = unbounded_channel();
953    let client_outgoing_to_server =
954        tokio_stream::wrappers::UnboundedReceiverStream::new(client_outgoing_to_server);
955    let mut client_incoming = Client::window()
956        .window_rule(client_outgoing_to_server)
957        .block_on_tokio()
958        .unwrap()
959        .into_inner();
960
961    let fut = async move {
962        while let Some(Ok(response)) = client_incoming.next().await {
963            let Some(response) = response.response else {
964                continue;
965            };
966
967            match response {
968                window::v1::window_rule_response::Response::NewWindow(new_window_request) => {
969                    let request_id = new_window_request.request_id;
970                    let window_id = new_window_request.window_id;
971
972                    for_all(WindowHandle { id: window_id });
973
974                    let sent = client_outgoing
975                        .send(window::v1::WindowRuleRequest {
976                            request: Some(window::v1::window_rule_request::Request::Finished(
977                                window::v1::window_rule_request::Finished { request_id },
978                            )),
979                        })
980                        .is_ok();
981
982                    if !sent {
983                        break;
984                    }
985                }
986            }
987        }
988    };
989
990    tokio::spawn(fut);
991}