pinnacle_api/
output.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//! Output management.
6//!
7//! An output is the Wayland term for a monitor. It presents windows, your cursor, and other UI elements.
8//!
9//! Outputs are uniquely identified by their name, a.k.a. the name of the connector they're plugged in to.
10
11use std::str::FromStr;
12
13use futures::FutureExt;
14use pinnacle_api_defs::pinnacle::{
15    output::{
16        self,
17        v1::{
18            GetEnabledRequest, GetFocusStackWindowIdsRequest, GetFocusedRequest, GetInfoRequest,
19            GetLocRequest, GetLogicalSizeRequest, GetModesRequest, GetPhysicalSizeRequest,
20            GetPoweredRequest, GetRequest, GetScaleRequest, GetTagIdsRequest, GetTransformRequest,
21            SetLocRequest, SetModeRequest, SetModelineRequest, SetPoweredRequest, SetScaleRequest,
22            SetTransformRequest,
23        },
24    },
25    util::v1::{AbsOrRel, SetOrToggle},
26};
27
28use crate::{
29    client::Client,
30    signal::{OutputSignal, SignalHandle},
31    tag::TagHandle,
32    util::{Batch, Point, Size},
33    window::WindowHandle,
34    BlockOnTokio,
35};
36
37/// Gets handles to all currently plugged-in outputs.
38///
39/// # Examples
40///
41/// ```no_run
42/// # use pinnacle_api::output;
43/// for output in output::get_all() {
44///     println!("{} {} {}", output.make(), output.model(), output.serial());
45/// }
46/// ```
47pub fn get_all() -> impl Iterator<Item = OutputHandle> {
48    get_all_async().block_on_tokio()
49}
50
51/// Async impl for [`get_all`].
52pub async fn get_all_async() -> impl Iterator<Item = OutputHandle> {
53    Client::output()
54        .get(GetRequest {})
55        .await
56        .unwrap()
57        .into_inner()
58        .output_names
59        .into_iter()
60        .map(|name| OutputHandle { name })
61}
62
63/// Gets handles to all currently plugged-in *and enabled* outputs.
64///
65/// This ignores outputs you have explicitly disabled.
66///
67/// # Examples
68///
69/// ```no_run
70/// # use pinnacle_api::output;
71/// for output in output::get_all_enabled() {
72///     println!("{} {} {}", output.make(), output.model(), output.serial());
73/// }
74/// ```
75pub fn get_all_enabled() -> impl Iterator<Item = OutputHandle> {
76    get_all_enabled_async().block_on_tokio()
77}
78
79/// Async impl for [`get_all_enabled`].
80pub async fn get_all_enabled_async() -> impl Iterator<Item = OutputHandle> {
81    get_all_async()
82        .await
83        .batch_filter(|op| op.enabled_async().boxed(), |enabled| enabled)
84}
85
86/// Gets a handle to the output with the given name.
87///
88/// By "name", we mean the name of the connector the output is connected to.
89pub fn get_by_name(name: impl ToString) -> Option<OutputHandle> {
90    get_by_name_async(name).block_on_tokio()
91}
92
93/// Async impl for [`get_by_name`].
94pub async fn get_by_name_async(name: impl ToString) -> Option<OutputHandle> {
95    get_all_async().await.find(|op| op.name == name.to_string())
96}
97
98/// Gets a handle to the currently focused output.
99///
100/// This is currently implemented as the one that has had the most recent pointer movement.
101pub fn get_focused() -> Option<OutputHandle> {
102    get_focused_async().block_on_tokio()
103}
104
105/// Async impl for [`get_focused`].
106pub async fn get_focused_async() -> Option<OutputHandle> {
107    get_all_async()
108        .await
109        .batch_find(|op| op.focused_async().boxed(), |focused| *focused)
110}
111
112/// Runs a closure on all current and future outputs.
113///
114/// When called, this will do two things:
115/// 1. Immediately run `for_each` with all currently connected outputs.
116/// 2. Call `for_each` with any newly connected outputs.
117///
118/// Note that `for_each` will *not* run with outputs that have been unplugged and replugged.
119/// This is to prevent duplicate setup. Instead, the compositor keeps track of any tags and
120/// state the output had when unplugged and restores them on replug. This may change in the future.
121///
122/// # Examples
123///
124/// ```no_run
125/// # use pinnacle_api::output;
126/// # use pinnacle_api::tag;
127/// // Add tags 1-3 to all outputs and set tag "1" to active
128/// output::for_each_output(|op| {
129///     let mut tags = tag::add(op, ["1", "2", "3"]);
130///     tags.next().unwrap().set_active(true);
131/// });
132/// ```
133pub fn for_each_output(mut for_each: impl FnMut(&OutputHandle) + Send + 'static) {
134    for output in get_all() {
135        for_each(&output);
136    }
137
138    Client::signal_state()
139        .output_connect
140        .add_callback(Box::new(for_each));
141}
142
143/// Connects to an [`OutputSignal`].
144///
145/// # Examples
146///
147/// ```no_run
148/// # use pinnacle_api::output;
149/// # use pinnacle_api::signal::OutputSignal;
150/// output::connect_signal(OutputSignal::Connect(Box::new(|output| {
151///     println!("New output: {}", output.name());
152/// })));
153/// ```
154pub fn connect_signal(signal: OutputSignal) -> SignalHandle {
155    let mut signal_state = Client::signal_state();
156
157    match signal {
158        OutputSignal::Connect(f) => signal_state.output_connect.add_callback(f),
159        OutputSignal::Disconnect(f) => signal_state.output_disconnect.add_callback(f),
160        OutputSignal::Resize(f) => signal_state.output_resize.add_callback(f),
161        OutputSignal::Move(f) => signal_state.output_move.add_callback(f),
162    }
163}
164
165/// A handle to an output.
166///
167/// This allows you to manipulate outputs and get their properties.
168#[derive(Clone, Debug, PartialEq, Eq, Hash)]
169pub struct OutputHandle {
170    pub(crate) name: String,
171}
172
173/// The alignment to use for [`OutputHandle::set_loc_adj_to`].
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
175pub enum Alignment {
176    /// Set above, align left borders
177    TopAlignLeft,
178    /// Set above, align centers
179    TopAlignCenter,
180    /// Set above, align right borders
181    TopAlignRight,
182    /// Set below, align left borders
183    BottomAlignLeft,
184    /// Set below, align centers
185    BottomAlignCenter,
186    /// Set below, align right borders
187    BottomAlignRight,
188    /// Set to left, align top borders
189    LeftAlignTop,
190    /// Set to left, align centers
191    LeftAlignCenter,
192    /// Set to left, align bottom borders
193    LeftAlignBottom,
194    /// Set to right, align top borders
195    RightAlignTop,
196    /// Set to right, align centers
197    RightAlignCenter,
198    /// Set to right, align bottom borders
199    RightAlignBottom,
200}
201
202/// An output transform.
203///
204/// This determines what orientation outputs will render with.
205#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
206#[repr(i32)]
207pub enum Transform {
208    /// No transform.
209    #[default]
210    Normal,
211    /// 90 degrees counter-clockwise.
212    _90,
213    /// 180 degrees counter-clockwise.
214    _180,
215    /// 270 degrees counter-clockwise.
216    _270,
217    /// Flipped vertically (across the horizontal axis).
218    Flipped,
219    /// Flipped vertically and rotated 90 degrees counter-clockwise
220    Flipped90,
221    /// Flipped vertically and rotated 180 degrees counter-clockwise
222    Flipped180,
223    /// Flipped vertically and rotated 270 degrees counter-clockwise
224    Flipped270,
225}
226
227impl TryFrom<output::v1::Transform> for Transform {
228    type Error = ();
229
230    fn try_from(value: output::v1::Transform) -> Result<Self, Self::Error> {
231        match value {
232            output::v1::Transform::Unspecified => Err(()),
233            output::v1::Transform::Normal => Ok(Transform::Normal),
234            output::v1::Transform::Transform90 => Ok(Transform::_90),
235            output::v1::Transform::Transform180 => Ok(Transform::_180),
236            output::v1::Transform::Transform270 => Ok(Transform::_270),
237            output::v1::Transform::Flipped => Ok(Transform::Flipped),
238            output::v1::Transform::Flipped90 => Ok(Transform::Flipped90),
239            output::v1::Transform::Flipped180 => Ok(Transform::Flipped180),
240            output::v1::Transform::Flipped270 => Ok(Transform::Flipped270),
241        }
242    }
243}
244
245impl From<Transform> for output::v1::Transform {
246    fn from(value: Transform) -> Self {
247        match value {
248            Transform::Normal => output::v1::Transform::Normal,
249            Transform::_90 => output::v1::Transform::Transform90,
250            Transform::_180 => output::v1::Transform::Transform180,
251            Transform::_270 => output::v1::Transform::Transform270,
252            Transform::Flipped => output::v1::Transform::Flipped,
253            Transform::Flipped90 => output::v1::Transform::Flipped90,
254            Transform::Flipped180 => output::v1::Transform::Flipped180,
255            Transform::Flipped270 => output::v1::Transform::Flipped270,
256        }
257    }
258}
259
260impl OutputHandle {
261    /// Creates an output handle from a name.
262    pub fn from_name(name: impl ToString) -> Self {
263        Self {
264            name: name.to_string(),
265        }
266    }
267
268    /// Sets the location of this output in the global space.
269    ///
270    /// On startup, Pinnacle will lay out all connected outputs starting at (0, 0)
271    /// and going to the right, with their top borders aligned.
272    ///
273    /// This method allows you to move outputs where necessary.
274    ///
275    /// Note: If you leave space between two outputs when setting their locations,
276    /// the pointer will not be able to move between them.
277    ///
278    /// # Examples
279    ///
280    /// ```no_run
281    /// # use pinnacle_api::output;
282    /// // Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
283    /// //  - "DP-1":   ┌─────┐
284    /// //              │     │1920x1080
285    /// //              └─────┘
286    /// //  - "HDMI-1": ┌───────┐
287    /// //              │ 2560x │
288    /// //              │ 1440  │
289    /// //              └───────┘
290    /// # || {
291    /// output::get_by_name("DP-1")?.set_loc(0, 0);
292    /// output::get_by_name("HDMI-1")?.set_loc(1920, -360);
293    /// # Some(())
294    /// # };
295    /// // Results in:
296    /// //   x=0    ┌───────┐y=-360
297    /// // y=0┌─────┤       │
298    /// //    │DP-1 │HDMI-1 │
299    /// //    └─────┴───────┘
300    /// //          ^x=1920
301    /// ```
302    pub fn set_loc(&self, x: i32, y: i32) {
303        Client::output()
304            .set_loc(SetLocRequest {
305                output_name: self.name(),
306                x,
307                y,
308            })
309            .block_on_tokio()
310            .unwrap();
311    }
312
313    /// Sets this output adjacent to another one.
314    ///
315    /// This is a helper method over [`OutputHandle::set_loc`] to make laying out outputs
316    /// easier.
317    ///
318    /// `alignment` is an [`Alignment`] of how you want this output to be placed.
319    /// For example, [`TopAlignLeft`][Alignment::TopAlignLeft] will place this output
320    /// above `other` and align the left borders.
321    /// Similarly, [`RightAlignCenter`][Alignment::RightAlignCenter] will place this output
322    /// to the right of `other` and align their centers.
323    ///
324    /// # Examples
325    ///
326    /// ```no_run
327    /// # use pinnacle_api::output;
328    /// # use pinnacle_api::output::Alignment;
329    /// // Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
330    /// //  - "DP-1":   ┌─────┐
331    /// //              │     │1920x1080
332    /// //              └─────┘
333    /// //  - "HDMI-1": ┌───────┐
334    /// //              │ 2560x │
335    /// //              │ 1440  │
336    /// //              └───────┘
337    /// # || {
338    /// let dp_1 = output::get_by_name("DP-1")?;
339    /// let hdmi_1 = output::get_by_name("HDMI-1")?;
340    /// dp_1.set_loc_adj_to(&hdmi_1, Alignment::BottomAlignRight);
341    /// # Some(())
342    /// # };
343    /// // Results in:
344    /// // ┌───────┐
345    /// // │       │
346    /// // │HDMI-1 │
347    /// // └──┬────┤
348    /// //    │DP-1│
349    /// //    └────┘
350    /// // Notice that "DP-1" now has the coordinates (2280, 1440) because "DP-1" is getting moved, not "HDMI-1".
351    /// // "HDMI-1" was placed at (1920, 0) during the compositor's initial output layout.
352    /// ```
353    pub fn set_loc_adj_to(&self, other: &OutputHandle, alignment: Alignment) {
354        let (self_size, other_loc, other_size) = async {
355            tokio::join!(
356                self.logical_size_async(),
357                other.loc_async(),
358                other.logical_size_async()
359            )
360        }
361        .block_on_tokio();
362
363        // poor man's try {}
364        let attempt_set_loc = || -> Option<()> {
365            let other_x = other_loc?.x;
366            let other_y = other_loc?.y;
367            let other_width = other_size?.w as i32;
368            let other_height = other_size?.h as i32;
369
370            let self_width = self_size?.w as i32;
371            let self_height = self_size?.h as i32;
372
373            use Alignment::*;
374
375            let x: i32;
376            let y: i32;
377
378            if let TopAlignLeft | TopAlignCenter | TopAlignRight | BottomAlignLeft
379            | BottomAlignCenter | BottomAlignRight = alignment
380            {
381                if let TopAlignLeft | TopAlignCenter | TopAlignRight = alignment {
382                    y = other_y - self_height;
383                } else {
384                    // bottom
385                    y = other_y + other_height;
386                }
387
388                match alignment {
389                    TopAlignLeft | BottomAlignLeft => x = other_x,
390                    TopAlignCenter | BottomAlignCenter => {
391                        x = other_x + (other_width - self_width) / 2;
392                    }
393                    TopAlignRight | BottomAlignRight => x = other_x + (other_width - self_width),
394                    _ => unreachable!(),
395                }
396            } else {
397                if let LeftAlignTop | LeftAlignCenter | LeftAlignBottom = alignment {
398                    x = other_x - self_width;
399                } else {
400                    x = other_x + other_width;
401                }
402
403                match alignment {
404                    LeftAlignTop | RightAlignTop => y = other_y,
405                    LeftAlignCenter | RightAlignCenter => {
406                        y = other_y + (other_height - self_height) / 2;
407                    }
408                    LeftAlignBottom | RightAlignBottom => {
409                        y = other_y + (other_height - self_height);
410                    }
411                    _ => unreachable!(),
412                }
413            }
414
415            self.set_loc(x, y);
416
417            Some(())
418        };
419
420        attempt_set_loc();
421    }
422
423    /// Sets this output's mode.
424    ///
425    /// If `refresh_rate_mhz` is provided, Pinnacle will attempt to use the mode with that
426    /// refresh rate. If it is not, Pinnacle will attempt to use the mode with the
427    /// highest refresh rate that matches the given size.
428    ///
429    /// The refresh rate should be given in millihertz. For example, if you want a refresh rate of
430    /// 60Hz, use 60000.
431    ///
432    /// If this output doesn't support the given mode, it will be ignored.
433    ///
434    /// # Examples
435    ///
436    /// ```no_run
437    /// # use pinnacle_api::output;
438    /// # || {
439    /// // Sets the focused output to 2560x1440 at 144Hz
440    /// output::get_focused()?.set_mode(2560, 1440, 144000);
441    /// # Some(())
442    /// # };
443    /// ```
444    pub fn set_mode(&self, width: u32, height: u32, refresh_rate_mhz: impl Into<Option<u32>>) {
445        Client::output()
446            .set_mode(SetModeRequest {
447                output_name: self.name(),
448                size: Some(pinnacle_api_defs::pinnacle::util::v1::Size { width, height }),
449                refresh_rate_mhz: refresh_rate_mhz.into(),
450                custom: false,
451            })
452            .block_on_tokio()
453            .unwrap();
454    }
455
456    /// Sets this output's mode to a custom one.
457    ///
458    /// If `refresh_rate_mhz` is provided, Pinnacle will create a new mode with that refresh rate.
459    /// If it is not, it will default to 60Hz.
460    ///
461    /// The refresh rate should be given in millihertz. For example, if you want a refresh rate of
462    /// 60Hz, use 60000.
463    ///
464    /// # Examples
465    ///
466    /// ```no_run
467    /// # use pinnacle_api::output;
468    /// # || {
469    /// // Sets the focused output to 2560x1440 at 75Hz
470    /// output::get_focused()?.set_custom_mode(2560, 1440, 75000);
471    /// # Some(())
472    /// # };
473    /// ```
474    pub fn set_custom_mode(
475        &self,
476        width: u32,
477        height: u32,
478        refresh_rate_mhz: impl Into<Option<u32>>,
479    ) {
480        Client::output()
481            .set_mode(SetModeRequest {
482                output_name: self.name(),
483                size: Some(pinnacle_api_defs::pinnacle::util::v1::Size { width, height }),
484                refresh_rate_mhz: refresh_rate_mhz.into(),
485                custom: true,
486            })
487            .block_on_tokio()
488            .unwrap();
489    }
490
491    /// Sets a custom modeline for this output.
492    ///
493    /// See `xorg.conf(5)` for more information.
494    ///
495    /// You can parse a modeline from a string of the form
496    /// "\<clock> \<hdisplay> \<hsync_start> \<hsync_end> \<htotal> \<vdisplay> \<vsync_start> \<vsync_end> \<hsync> \<vsync>".
497    ///
498    /// # Examples
499    ///
500    /// ```no_run
501    /// # use pinnacle_api::output;
502    /// # || {
503    /// let output = output::get_focused()?;
504    /// output.set_modeline("173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync".parse().unwrap());
505    /// # Some(())
506    /// # };
507    /// ```
508    pub fn set_modeline(&self, modeline: Modeline) {
509        Client::output()
510            .set_modeline(SetModelineRequest {
511                output_name: self.name(),
512                modeline: Some(modeline.into()),
513            })
514            .block_on_tokio()
515            .unwrap();
516    }
517
518    /// Sets this output's scaling factor.
519    pub fn set_scale(&self, scale: f32) {
520        Client::output()
521            .set_scale(SetScaleRequest {
522                output_name: self.name(),
523                scale,
524                abs_or_rel: AbsOrRel::Absolute.into(),
525            })
526            .block_on_tokio()
527            .unwrap();
528    }
529
530    /// Changes this output's scaling factor by a relative amount.
531    ///
532    /// # Examples
533    ///
534    /// ```no_run
535    /// # use pinnacle_api::output;
536    /// # || {
537    /// output::get_focused()?.change_scale(0.25);
538    /// output::get_focused()?.change_scale(-0.25);
539    /// # Some(())
540    /// # };
541    /// ```
542    pub fn change_scale(&self, change_by: f32) {
543        Client::output()
544            .set_scale(SetScaleRequest {
545                output_name: self.name(),
546                scale: change_by,
547                abs_or_rel: AbsOrRel::Relative.into(),
548            })
549            .block_on_tokio()
550            .unwrap();
551    }
552
553    /// Sets this output's [`Transform`].
554    ///
555    /// # Examples
556    ///
557    /// ```no_run
558    /// # use pinnacle_api::output;
559    /// # use pinnacle_api::output::Transform;
560    /// // Rotate 90 degrees counter-clockwise
561    /// # || {
562    /// output::get_focused()?.set_transform(Transform::_90);
563    /// # Some(())
564    /// # };
565    /// ```
566    pub fn set_transform(&self, transform: Transform) {
567        Client::output()
568            .set_transform(SetTransformRequest {
569                output_name: self.name(),
570                transform: output::v1::Transform::from(transform).into(),
571            })
572            .block_on_tokio()
573            .unwrap();
574    }
575
576    /// Powers on or off this output.
577    ///
578    /// This will not remove it from the space and your tags and windows
579    /// will still be interactable; only the monitor is turned off.
580    pub fn set_powered(&self, powered: bool) {
581        Client::output()
582            .set_powered(SetPoweredRequest {
583                output_name: self.name(),
584                set_or_toggle: match powered {
585                    true => SetOrToggle::Set,
586                    false => SetOrToggle::Unset,
587                }
588                .into(),
589            })
590            .block_on_tokio()
591            .unwrap();
592    }
593
594    /// Toggles the power on this output.
595    ///
596    /// This will not remove it from the space and your tags and windows
597    /// will still be interactable; only the monitor is turned off.
598    pub fn toggle_powered(&self) {
599        Client::output()
600            .set_powered(SetPoweredRequest {
601                output_name: self.name(),
602                set_or_toggle: SetOrToggle::Toggle.into(),
603            })
604            .block_on_tokio()
605            .unwrap();
606    }
607
608    /// Gets this output's make.
609    pub fn make(&self) -> String {
610        self.make_async().block_on_tokio()
611    }
612
613    /// Async impl for [`Self::make`].
614    pub async fn make_async(&self) -> String {
615        Client::output()
616            .get_info(GetInfoRequest {
617                output_name: self.name(),
618            })
619            .await
620            .unwrap()
621            .into_inner()
622            .make
623    }
624
625    /// Gets this output's model.
626    pub fn model(&self) -> String {
627        self.model_async().block_on_tokio()
628    }
629
630    /// Async impl for [`Self::model`].
631    pub async fn model_async(&self) -> String {
632        Client::output()
633            .get_info(GetInfoRequest {
634                output_name: self.name(),
635            })
636            .await
637            .unwrap()
638            .into_inner()
639            .model
640    }
641
642    /// Gets this output's serial.
643    pub fn serial(&self) -> String {
644        self.serial_async().block_on_tokio()
645    }
646
647    /// Async impl for [`Self::serial`].
648    pub async fn serial_async(&self) -> String {
649        Client::output()
650            .get_info(GetInfoRequest {
651                output_name: self.name(),
652            })
653            .await
654            .unwrap()
655            .into_inner()
656            .serial
657    }
658
659    /// Gets this output's location in the global space.
660    ///
661    /// May return `None` if it is disabled.
662    pub fn loc(&self) -> Option<Point> {
663        self.loc_async().block_on_tokio()
664    }
665
666    /// Async impl for [`Self::loc`].
667    pub async fn loc_async(&self) -> Option<Point> {
668        Client::output()
669            .get_loc(GetLocRequest {
670                output_name: self.name(),
671            })
672            .await
673            .unwrap()
674            .into_inner()
675            .loc
676            .map(|loc| Point { x: loc.x, y: loc.y })
677    }
678
679    /// Gets this output's logical size in logical pixels.
680    ///
681    /// If this output has a scale of 1, this will equal the output's
682    /// actual pixel size. If it has a scale of 2, it will have half the
683    /// logical pixel width and height. Similarly, if it has a scale of 0.5,
684    /// it will have double the logical pixel width and height.
685    ///
686    /// May return `None` if it is disabled.
687    pub fn logical_size(&self) -> Option<Size> {
688        self.logical_size_async().block_on_tokio()
689    }
690
691    /// Async impl for [`Self::logical_size`].
692    pub async fn logical_size_async(&self) -> Option<Size> {
693        Client::output()
694            .get_logical_size(GetLogicalSizeRequest {
695                output_name: self.name(),
696            })
697            .await
698            .unwrap()
699            .into_inner()
700            .logical_size
701            .map(|size| Size {
702                w: size.width,
703                h: size.height,
704            })
705    }
706
707    /// Gets this output's current mode.
708    ///
709    /// May return `None` if it is disabled.
710    pub fn current_mode(&self) -> Option<Mode> {
711        self.current_mode_async().block_on_tokio()
712    }
713
714    /// Async impl for [`Self::current_mode`].
715    pub async fn current_mode_async(&self) -> Option<Mode> {
716        Client::output()
717            .get_modes(GetModesRequest {
718                output_name: self.name(),
719            })
720            .await
721            .unwrap()
722            .into_inner()
723            .current_mode
724            .map(|mode| Mode {
725                size: Size {
726                    w: mode.size.expect("mode should have a size").width,
727                    h: mode.size.expect("mode should have a size").height,
728                },
729                refresh_rate_mhz: mode.refresh_rate_mhz,
730            })
731    }
732
733    /// Gets this output's preferred mode.
734    ///
735    /// May return `None` if it is disabled.
736    pub fn preferred_mode(&self) -> Option<Mode> {
737        self.preferred_mode_async().block_on_tokio()
738    }
739
740    /// Async impl for [`Self::preferred_mode`].
741    pub async fn preferred_mode_async(&self) -> Option<Mode> {
742        Client::output()
743            .get_modes(GetModesRequest {
744                output_name: self.name(),
745            })
746            .await
747            .unwrap()
748            .into_inner()
749            .preferred_mode
750            .map(|mode| Mode {
751                size: Size {
752                    w: mode.size.expect("mode should have a size").width,
753                    h: mode.size.expect("mode should have a size").height,
754                },
755                refresh_rate_mhz: mode.refresh_rate_mhz,
756            })
757    }
758
759    /// Gets all modes currently known to this output.
760    pub fn modes(&self) -> impl Iterator<Item = Mode> {
761        self.modes_async().block_on_tokio()
762    }
763
764    /// Async impl for [`Self::modes`].
765    pub async fn modes_async(&self) -> impl Iterator<Item = Mode> {
766        Client::output()
767            .get_modes(GetModesRequest {
768                output_name: self.name(),
769            })
770            .await
771            .unwrap()
772            .into_inner()
773            .modes
774            .into_iter()
775            .map(|mode| Mode {
776                size: Size {
777                    w: mode.size.expect("mode should have a size").width,
778                    h: mode.size.expect("mode should have a size").height,
779                },
780                refresh_rate_mhz: mode.refresh_rate_mhz,
781            })
782    }
783
784    /// Gets this output's physical size in millimeters.
785    ///
786    /// Returns a size of (0, 0) if unknown.
787    pub fn physical_size(&self) -> Size {
788        self.physical_size_async().block_on_tokio()
789    }
790
791    /// Async impl for [`Self::physical_size`].
792    pub async fn physical_size_async(&self) -> Size {
793        Client::output()
794            .get_physical_size(GetPhysicalSizeRequest {
795                output_name: self.name(),
796            })
797            .await
798            .unwrap()
799            .into_inner()
800            .physical_size
801            .map(|size| Size {
802                w: size.width,
803                h: size.height,
804            })
805            .unwrap_or_default()
806    }
807
808    /// Gets whether or not this output is focused.
809    ///
810    /// This is currently implemented as the output with the most recent pointer motion.
811    pub fn focused(&self) -> bool {
812        self.focused_async().block_on_tokio()
813    }
814
815    /// Async impl for [`Self::focused`].
816    pub async fn focused_async(&self) -> bool {
817        Client::output()
818            .get_focused(GetFocusedRequest {
819                output_name: self.name(),
820            })
821            .await
822            .unwrap()
823            .into_inner()
824            .focused
825    }
826
827    /// Gets handles to all tags on this output.
828    pub fn tags(&self) -> impl Iterator<Item = TagHandle> {
829        self.tags_async().block_on_tokio()
830    }
831
832    /// Async impl for [`Self::tags`].
833    pub async fn tags_async(&self) -> impl Iterator<Item = TagHandle> {
834        Client::output()
835            .get_tag_ids(GetTagIdsRequest {
836                output_name: self.name(),
837            })
838            .await
839            .unwrap()
840            .into_inner()
841            .tag_ids
842            .into_iter()
843            .map(|id| TagHandle { id })
844    }
845
846    /// Gets this output's current scale.
847    pub fn scale(&self) -> f32 {
848        self.scale_async().block_on_tokio()
849    }
850
851    /// Async impl for [`Self::scale`].
852    pub async fn scale_async(&self) -> f32 {
853        Client::output()
854            .get_scale(GetScaleRequest {
855                output_name: self.name(),
856            })
857            .await
858            .unwrap()
859            .into_inner()
860            .scale
861    }
862
863    /// Gets this output's current transform.
864    pub fn transform(&self) -> Transform {
865        self.transform_async().block_on_tokio()
866    }
867
868    /// Async impl for [`Self::transform`].
869    pub async fn transform_async(&self) -> Transform {
870        Client::output()
871            .get_transform(GetTransformRequest {
872                output_name: self.name(),
873            })
874            .await
875            .unwrap()
876            .into_inner()
877            .transform()
878            .try_into()
879            .unwrap_or_default()
880    }
881
882    /// Gets this window's keyboard focus stack.
883    ///
884    /// Pinnacle keeps a stack of the windows that get keyboard focus.
885    /// This can be used, for example, for an `Alt + Tab`-style keybind
886    /// that focuses the previously focused window.
887    ///
888    /// This will return the focus stack containing *all* windows on this output.
889    /// If you only want windows on active tags, see
890    /// [`OutputHandle::keyboard_focus_stack_visible`].
891    pub fn keyboard_focus_stack(&self) -> impl Iterator<Item = WindowHandle> {
892        self.keyboard_focus_stack_async().block_on_tokio()
893    }
894
895    /// Async impl for [`Self::keyboard_focus_stack`].
896    pub async fn keyboard_focus_stack_async(&self) -> impl Iterator<Item = WindowHandle> {
897        Client::output()
898            .get_focus_stack_window_ids(GetFocusStackWindowIdsRequest {
899                output_name: self.name(),
900            })
901            .await
902            .unwrap()
903            .into_inner()
904            .window_ids
905            .into_iter()
906            .map(|id| WindowHandle { id })
907    }
908
909    /// Gets this window's keyboard focus stack with only windows on active tags.
910    ///
911    /// Pinnacle keeps a stack of the windows that get keyboard focus.
912    /// This can be used, for example, for an `Alt + Tab`-style keybind
913    /// that focuses the previously focused window.
914    ///
915    /// This will return the focus stack containing only windows on active tags on this output.
916    /// If you want *all* windows on this output, see [`OutputHandle::keyboard_focus_stack`].
917    pub fn keyboard_focus_stack_visible(&self) -> impl Iterator<Item = WindowHandle> {
918        self.keyboard_focus_stack_visible_async().block_on_tokio()
919    }
920
921    /// Async impl for [`Self::keyboard_focus_stack_visible`].
922    pub async fn keyboard_focus_stack_visible_async(&self) -> impl Iterator<Item = WindowHandle> {
923        self.keyboard_focus_stack_async()
924            .await
925            .batch_filter(|win| win.is_on_active_tag_async().boxed(), |is_on| is_on)
926    }
927
928    /// Gets whether this output is enabled.
929    pub fn enabled(&self) -> bool {
930        self.enabled_async().block_on_tokio()
931    }
932
933    /// Async impl for [`Self::enabled`].
934    pub async fn enabled_async(&self) -> bool {
935        Client::output()
936            .get_enabled(GetEnabledRequest {
937                output_name: self.name(),
938            })
939            .await
940            .unwrap()
941            .into_inner()
942            .enabled
943    }
944
945    /// Gets whether or not this output is powered.
946    ///
947    /// Unpowered outputs are turned off but you can still interact with them.
948    pub fn powered(&self) -> bool {
949        self.powered_async().block_on_tokio()
950    }
951
952    /// Async impl for [`Self::powered`].
953    pub async fn powered_async(&self) -> bool {
954        Client::output()
955            .get_powered(GetPoweredRequest {
956                output_name: self.name(),
957            })
958            .await
959            .unwrap()
960            .into_inner()
961            .powered
962    }
963
964    /// Returns this output's unique name (the name of its connector).
965    pub fn name(&self) -> String {
966        self.name.to_string()
967    }
968}
969
970/// A possible output pixel dimension and refresh rate configuration.
971#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
972pub struct Mode {
973    /// The size of the mode, in pixels.
974    pub size: Size,
975    /// The output's refresh rate, in millihertz.
976    ///
977    /// For example, 60Hz is returned as 60000.
978    pub refresh_rate_mhz: u32,
979}
980
981/// A custom modeline.
982#[allow(missing_docs)]
983#[derive(Copy, Clone, Debug, PartialEq, Default)]
984pub struct Modeline {
985    pub clock: f32,
986    pub hdisplay: u32,
987    pub hsync_start: u32,
988    pub hsync_end: u32,
989    pub htotal: u32,
990    pub vdisplay: u32,
991    pub vsync_start: u32,
992    pub vsync_end: u32,
993    pub vtotal: u32,
994    pub hsync: bool,
995    pub vsync: bool,
996}
997
998impl From<Modeline> for output::v1::Modeline {
999    fn from(modeline: Modeline) -> Self {
1000        output::v1::Modeline {
1001            clock: modeline.clock,
1002            hdisplay: modeline.hdisplay,
1003            hsync_start: modeline.hsync_start,
1004            hsync_end: modeline.hsync_end,
1005            htotal: modeline.htotal,
1006            vdisplay: modeline.vdisplay,
1007            vsync_start: modeline.vsync_start,
1008            vsync_end: modeline.vsync_end,
1009            vtotal: modeline.vtotal,
1010            hsync: modeline.hsync,
1011            vsync: modeline.vsync,
1012        }
1013    }
1014}
1015
1016/// Error for the `FromStr` implementation for [`Modeline`].
1017#[derive(Debug)]
1018pub struct ParseModelineError(ParseModelineErrorKind);
1019
1020#[derive(Debug)]
1021enum ParseModelineErrorKind {
1022    NoClock,
1023    InvalidClock,
1024    NoHdisplay,
1025    InvalidHdisplay,
1026    NoHsyncStart,
1027    InvalidHsyncStart,
1028    NoHsyncEnd,
1029    InvalidHsyncEnd,
1030    NoHtotal,
1031    InvalidHtotal,
1032    NoVdisplay,
1033    InvalidVdisplay,
1034    NoVsyncStart,
1035    InvalidVsyncStart,
1036    NoVsyncEnd,
1037    InvalidVsyncEnd,
1038    NoVtotal,
1039    InvalidVtotal,
1040    NoHsync,
1041    InvalidHsync,
1042    NoVsync,
1043    InvalidVsync,
1044}
1045
1046impl std::fmt::Display for ParseModelineError {
1047    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1048        std::fmt::Debug::fmt(&self.0, f)
1049    }
1050}
1051
1052impl From<ParseModelineErrorKind> for ParseModelineError {
1053    fn from(value: ParseModelineErrorKind) -> Self {
1054        Self(value)
1055    }
1056}
1057
1058impl FromStr for Modeline {
1059    type Err = ParseModelineError;
1060
1061    /// Tries to convert the provided modeline string to a [`Modeline`].
1062    fn from_str(s: &str) -> Result<Self, Self::Err> {
1063        let mut args = s.split_whitespace();
1064
1065        let clock = args
1066            .next()
1067            .ok_or(ParseModelineErrorKind::NoClock)?
1068            .parse()
1069            .map_err(|_| ParseModelineErrorKind::InvalidClock)?;
1070        let hdisplay = args
1071            .next()
1072            .ok_or(ParseModelineErrorKind::NoHdisplay)?
1073            .parse()
1074            .map_err(|_| ParseModelineErrorKind::InvalidHdisplay)?;
1075        let hsync_start = args
1076            .next()
1077            .ok_or(ParseModelineErrorKind::NoHsyncStart)?
1078            .parse()
1079            .map_err(|_| ParseModelineErrorKind::InvalidHsyncStart)?;
1080        let hsync_end = args
1081            .next()
1082            .ok_or(ParseModelineErrorKind::NoHsyncEnd)?
1083            .parse()
1084            .map_err(|_| ParseModelineErrorKind::InvalidHsyncEnd)?;
1085        let htotal = args
1086            .next()
1087            .ok_or(ParseModelineErrorKind::NoHtotal)?
1088            .parse()
1089            .map_err(|_| ParseModelineErrorKind::InvalidHtotal)?;
1090        let vdisplay = args
1091            .next()
1092            .ok_or(ParseModelineErrorKind::NoVdisplay)?
1093            .parse()
1094            .map_err(|_| ParseModelineErrorKind::InvalidVdisplay)?;
1095        let vsync_start = args
1096            .next()
1097            .ok_or(ParseModelineErrorKind::NoVsyncStart)?
1098            .parse()
1099            .map_err(|_| ParseModelineErrorKind::InvalidVsyncStart)?;
1100        let vsync_end = args
1101            .next()
1102            .ok_or(ParseModelineErrorKind::NoVsyncEnd)?
1103            .parse()
1104            .map_err(|_| ParseModelineErrorKind::InvalidVsyncEnd)?;
1105        let vtotal = args
1106            .next()
1107            .ok_or(ParseModelineErrorKind::NoVtotal)?
1108            .parse()
1109            .map_err(|_| ParseModelineErrorKind::InvalidVtotal)?;
1110
1111        let hsync = match args
1112            .next()
1113            .ok_or(ParseModelineErrorKind::NoHsync)?
1114            .to_lowercase()
1115            .as_str()
1116        {
1117            "+hsync" => true,
1118            "-hsync" => false,
1119            _ => Err(ParseModelineErrorKind::InvalidHsync)?,
1120        };
1121        let vsync = match args
1122            .next()
1123            .ok_or(ParseModelineErrorKind::NoVsync)?
1124            .to_lowercase()
1125            .as_str()
1126        {
1127            "+vsync" => true,
1128            "-vsync" => false,
1129            _ => Err(ParseModelineErrorKind::InvalidVsync)?,
1130        };
1131
1132        Ok(Modeline {
1133            clock,
1134            hdisplay,
1135            hsync_start,
1136            hsync_end,
1137            htotal,
1138            vdisplay,
1139            vsync_start,
1140            vsync_end,
1141            vtotal,
1142            hsync,
1143            vsync,
1144        })
1145    }
1146}