2024-01-23 04:04:08 +01:00
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
2023-10-21 00:02:00 +02:00
|
|
|
//! Output management.
|
2024-01-22 06:45:09 +01:00
|
|
|
//!
|
|
|
|
//! An output is Pinnacle's terminology for a monitor.
|
|
|
|
//!
|
|
|
|
//! This module provides [`Output`], which allows you to get [`OutputHandle`]s for different
|
|
|
|
//! connected monitors and set them up.
|
|
|
|
|
2024-02-23 23:24:43 +01:00
|
|
|
use futures::FutureExt;
|
|
|
|
use pinnacle_api_defs::pinnacle::output::{
|
|
|
|
self,
|
2024-03-23 00:58:31 +01:00
|
|
|
v0alpha1::{output_service_client::OutputServiceClient, SetLocationRequest, SetModeRequest},
|
2023-10-20 01:18:34 +02:00
|
|
|
};
|
2024-01-22 06:45:09 +01:00
|
|
|
use tonic::transport::Channel;
|
2023-10-20 01:18:34 +02:00
|
|
|
|
2024-02-23 23:24:43 +01:00
|
|
|
use crate::{
|
|
|
|
block_on_tokio,
|
|
|
|
signal::{OutputSignal, SignalHandle},
|
|
|
|
tag::TagHandle,
|
|
|
|
util::Batch,
|
|
|
|
SIGNAL, TAG,
|
|
|
|
};
|
2023-10-20 01:18:34 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// A struct that allows you to get handles to connected outputs and set them up.
|
2023-10-21 00:02:00 +02:00
|
|
|
///
|
2024-01-22 06:45:09 +01:00
|
|
|
/// See [`OutputHandle`] for more information.
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Output {
|
2024-02-21 04:35:51 +01:00
|
|
|
output_client: OutputServiceClient<Channel>,
|
2023-10-21 00:02:00 +02:00
|
|
|
}
|
2023-10-20 01:18:34 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
impl Output {
|
2024-02-23 23:24:43 +01:00
|
|
|
pub(crate) fn new(channel: Channel) -> Self {
|
2024-01-22 06:45:09 +01:00
|
|
|
Self {
|
2024-02-21 04:35:51 +01:00
|
|
|
output_client: OutputServiceClient::new(channel.clone()),
|
2024-02-23 23:24:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn new_handle(&self, name: impl Into<String>) -> OutputHandle {
|
|
|
|
OutputHandle {
|
|
|
|
name: name.into(),
|
|
|
|
output_client: self.output_client.clone(),
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
|
|
|
}
|
2023-10-20 01:18:34 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get a handle to all connected outputs.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let outputs = output.get_all();
|
|
|
|
/// ```
|
2024-02-23 23:24:43 +01:00
|
|
|
pub fn get_all(&self) -> Vec<OutputHandle> {
|
2024-02-21 04:35:51 +01:00
|
|
|
block_on_tokio(self.get_all_async())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The async version of [`Output::get_all`].
|
2024-02-23 23:24:43 +01:00
|
|
|
pub async fn get_all_async(&self) -> Vec<OutputHandle> {
|
2024-02-21 04:35:51 +01:00
|
|
|
let mut client = self.output_client.clone();
|
2024-02-23 23:24:43 +01:00
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
client
|
|
|
|
.get(output::v0alpha1::GetRequest {})
|
|
|
|
.await
|
2024-01-22 06:45:09 +01:00
|
|
|
.unwrap()
|
|
|
|
.into_inner()
|
|
|
|
.output_names
|
|
|
|
.into_iter()
|
2024-02-23 23:24:43 +01:00
|
|
|
.map(move |name| self.new_handle(name))
|
|
|
|
.collect()
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a handle to the output with the given name.
|
|
|
|
///
|
|
|
|
/// By "name", we mean the name of the connector the output is connected to.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let op = output.get_by_name("eDP-1")?;
|
|
|
|
/// let op2 = output.get_by_name("HDMI-2")?;
|
|
|
|
/// ```
|
|
|
|
pub fn get_by_name(&self, name: impl Into<String>) -> Option<OutputHandle> {
|
2024-02-21 04:35:51 +01:00
|
|
|
block_on_tokio(self.get_by_name_async(name))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The async version of [`Output::get_by_name`].
|
|
|
|
pub async fn get_by_name_async(&self, name: impl Into<String>) -> Option<OutputHandle> {
|
2024-01-22 06:45:09 +01:00
|
|
|
let name: String = name.into();
|
2024-02-21 04:35:51 +01:00
|
|
|
self.get_all_async()
|
|
|
|
.await
|
2024-02-23 23:24:43 +01:00
|
|
|
.into_iter()
|
2024-02-21 04:35:51 +01:00
|
|
|
.find(|output| output.name == name)
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
2023-10-20 02:26:12 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get a handle to the focused output.
|
|
|
|
///
|
|
|
|
/// This is currently implemented as the one that has had the most recent pointer movement.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// let op = output.get_focused()?;
|
|
|
|
/// ```
|
|
|
|
pub fn get_focused(&self) -> Option<OutputHandle> {
|
|
|
|
self.get_all()
|
2024-02-23 23:24:43 +01:00
|
|
|
.into_iter()
|
2024-01-22 06:45:09 +01:00
|
|
|
.find(|output| matches!(output.props().focused, Some(true)))
|
|
|
|
}
|
2023-10-20 02:26:12 +02:00
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`Output::get_focused`].
|
|
|
|
pub async fn get_focused_async(&self) -> Option<OutputHandle> {
|
|
|
|
self.get_all_async().await.batch_find(
|
|
|
|
|output| output.props_async().boxed(),
|
|
|
|
|props| props.focused.is_some_and(|focused| focused),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Connect a closure to be run on all current and future outputs.
|
|
|
|
///
|
|
|
|
/// When called, `connect_for_all` will do two things:
|
|
|
|
/// 1. Immediately run `for_all` with all currently connected outputs.
|
|
|
|
/// 2. Create a future that will call `for_all` with any newly connected outputs.
|
|
|
|
///
|
|
|
|
/// Note that `for_all` will *not* run with outputs that have been unplugged and replugged.
|
|
|
|
/// This is to prevent duplicate setup. Instead, the compositor keeps track of any tags and
|
|
|
|
/// state the output had when unplugged and restores them on replug.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// // Add tags 1-3 to all outputs and set tag "1" to active
|
|
|
|
/// output.connect_for_all(|op| {
|
|
|
|
/// let tags = tag.add(&op, ["1", "2", "3"]);
|
|
|
|
/// tags.next().unwrap().set_active(true);
|
|
|
|
/// });
|
|
|
|
/// ```
|
2024-02-23 23:24:43 +01:00
|
|
|
pub fn connect_for_all(&self, mut for_all: impl FnMut(&OutputHandle) + Send + 'static) {
|
2024-01-22 06:45:09 +01:00
|
|
|
for output in self.get_all() {
|
2024-02-23 23:24:43 +01:00
|
|
|
for_all(&output);
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
2023-10-20 02:26:12 +02:00
|
|
|
|
2024-02-23 23:24:43 +01:00
|
|
|
let mut signal_state = block_on_tokio(SIGNAL.get().expect("SIGNAL doesn't exist").write());
|
|
|
|
signal_state.output_connect.add_callback(Box::new(for_all));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Connect to an output signal.
|
|
|
|
///
|
|
|
|
/// The compositor will fire off signals that your config can listen for and act upon.
|
|
|
|
/// You can pass in an [`OutputSignal`] along with a callback and it will get run
|
|
|
|
/// with the necessary arguments every time a signal of that type is received.
|
|
|
|
pub fn connect_signal(&self, signal: OutputSignal) -> SignalHandle {
|
|
|
|
let mut signal_state = block_on_tokio(SIGNAL.get().expect("SIGNAL doesn't exist").write());
|
|
|
|
|
|
|
|
match signal {
|
|
|
|
OutputSignal::Connect(f) => signal_state.output_connect.add_callback(f),
|
|
|
|
}
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
2023-10-20 01:18:34 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// A handle to an output.
|
2023-10-20 01:18:34 +02:00
|
|
|
///
|
2024-01-22 06:45:09 +01:00
|
|
|
/// This allows you to manipulate outputs and get their properties.
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct OutputHandle {
|
|
|
|
pub(crate) name: String,
|
2024-02-23 23:24:43 +01:00
|
|
|
output_client: OutputServiceClient<Channel>,
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
2023-10-20 01:18:34 +02:00
|
|
|
|
2024-01-23 01:36:04 +01:00
|
|
|
impl PartialEq for OutputHandle {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.name == other.name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for OutputHandle {}
|
|
|
|
|
|
|
|
impl std::hash::Hash for OutputHandle {
|
|
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
self.name.hash(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// The alignment to use for [`OutputHandle::set_loc_adj_to`].
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum Alignment {
|
|
|
|
/// Set above, align left borders
|
|
|
|
TopAlignLeft,
|
|
|
|
/// Set above, align centers
|
|
|
|
TopAlignCenter,
|
|
|
|
/// Set above, align right borders
|
|
|
|
TopAlignRight,
|
|
|
|
/// Set below, align left borders
|
|
|
|
BottomAlignLeft,
|
|
|
|
/// Set below, align centers
|
|
|
|
BottomAlignCenter,
|
|
|
|
/// Set below, align right borders
|
|
|
|
BottomAlignRight,
|
|
|
|
/// Set to left, align top borders
|
|
|
|
LeftAlignTop,
|
|
|
|
/// Set to left, align centers
|
|
|
|
LeftAlignCenter,
|
|
|
|
/// Set to left, align bottom borders
|
|
|
|
LeftAlignBottom,
|
|
|
|
/// Set to right, align top borders
|
|
|
|
RightAlignTop,
|
|
|
|
/// Set to right, align centers
|
|
|
|
RightAlignCenter,
|
|
|
|
/// Set to right, align bottom borders
|
|
|
|
RightAlignBottom,
|
2023-10-20 01:18:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl OutputHandle {
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Set the location of this output in the global space.
|
|
|
|
///
|
|
|
|
/// On startup, Pinnacle will lay out all connected outputs starting at (0, 0)
|
|
|
|
/// and going to the right, with their top borders aligned.
|
|
|
|
///
|
|
|
|
/// This method allows you to move outputs where necessary.
|
|
|
|
///
|
|
|
|
/// Note: If you leave space between two outputs when setting their locations,
|
|
|
|
/// the pointer will not be able to move between them.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// // Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
|
|
|
|
/// // - "DP-1": ┌─────┐
|
|
|
|
/// // │ │1920x1080
|
|
|
|
/// // └─────┘
|
|
|
|
/// // - "HDMI-1": ┌───────┐
|
|
|
|
/// // │ 2560x │
|
|
|
|
/// // │ 1440 │
|
|
|
|
/// // └───────┘
|
|
|
|
///
|
|
|
|
/// output.get_by_name("DP-1")?.set_location(0, 0);
|
|
|
|
/// output.get_by_name("HDMI-1")?.set_location(1920, -360);
|
|
|
|
///
|
|
|
|
/// // Results in:
|
|
|
|
/// // x=0 ┌───────┐y=-360
|
|
|
|
/// // y=0┌─────┤ │
|
|
|
|
/// // │DP-1 │HDMI-1 │
|
|
|
|
/// // └─────┴───────┘
|
|
|
|
/// // ^x=1920
|
|
|
|
/// ```
|
|
|
|
pub fn set_location(&self, x: impl Into<Option<i32>>, y: impl Into<Option<i32>>) {
|
2024-02-21 04:35:51 +01:00
|
|
|
let mut client = self.output_client.clone();
|
2024-01-26 03:14:20 +01:00
|
|
|
block_on_tokio(client.set_location(SetLocationRequest {
|
2024-01-22 06:45:09 +01:00
|
|
|
output_name: Some(self.name.clone()),
|
|
|
|
x: x.into(),
|
|
|
|
y: y.into(),
|
|
|
|
}))
|
|
|
|
.unwrap();
|
2023-10-21 00:02:00 +02:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Set this output adjacent to another one.
|
|
|
|
///
|
|
|
|
/// This is a helper method over [`OutputHandle::set_location`] to make laying out outputs
|
|
|
|
/// easier.
|
|
|
|
///
|
|
|
|
/// `alignment` is an [`Alignment`] of how you want this output to be placed.
|
|
|
|
/// For example, [`TopAlignLeft`][Alignment::TopAlignLeft] will place this output
|
|
|
|
/// above `other` and align the left borders.
|
|
|
|
/// Similarly, [`RightAlignCenter`][Alignment::RightAlignCenter] will place this output
|
|
|
|
/// to the right of `other` and align their centers.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use pinnacle_api::output::Alignment;
|
|
|
|
///
|
|
|
|
/// // Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
|
|
|
|
/// // - "DP-1": ┌─────┐
|
|
|
|
/// // │ │1920x1080
|
|
|
|
/// // └─────┘
|
|
|
|
/// // - "HDMI-1": ┌───────┐
|
|
|
|
/// // │ 2560x │
|
|
|
|
/// // │ 1440 │
|
|
|
|
/// // └───────┘
|
|
|
|
///
|
|
|
|
/// output.get_by_name("DP-1")?.set_loc_adj_to(output.get_by_name("HDMI-1")?, Alignment::BottomAlignRight);
|
|
|
|
///
|
|
|
|
/// // Results in:
|
|
|
|
/// // ┌───────┐
|
|
|
|
/// // │ │
|
|
|
|
/// // │HDMI-1 │
|
|
|
|
/// // └──┬────┤
|
|
|
|
/// // │DP-1│
|
|
|
|
/// // └────┘
|
|
|
|
/// // Notice that "DP-1" now has the coordinates (2280, 1440) because "DP-1" is getting moved, not "HDMI-1".
|
|
|
|
/// // "HDMI-1" was placed at (1920, 0) during the compositor's initial output layout.
|
|
|
|
/// ```
|
|
|
|
pub fn set_loc_adj_to(&self, other: &OutputHandle, alignment: Alignment) {
|
|
|
|
let self_props = self.props();
|
|
|
|
let other_props = other.props();
|
|
|
|
|
|
|
|
// poor man's try {}
|
|
|
|
let attempt_set_loc = || -> Option<()> {
|
|
|
|
let other_x = other_props.x?;
|
|
|
|
let other_y = other_props.y?;
|
2024-03-23 00:37:27 +01:00
|
|
|
let other_mode = other_props.current_mode?;
|
|
|
|
let other_width = other_mode.pixel_width as i32;
|
|
|
|
let other_height = other_mode.pixel_height as i32;
|
2024-01-22 06:45:09 +01:00
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
let self_mode = self_props.current_mode?;
|
|
|
|
let self_width = self_mode.pixel_width as i32;
|
|
|
|
let self_height = self_mode.pixel_height as i32;
|
2024-01-22 06:45:09 +01:00
|
|
|
|
|
|
|
use Alignment::*;
|
|
|
|
|
|
|
|
let x: i32;
|
|
|
|
let y: i32;
|
|
|
|
|
|
|
|
if let TopAlignLeft | TopAlignCenter | TopAlignRight | BottomAlignLeft
|
|
|
|
| BottomAlignCenter | BottomAlignRight = alignment
|
|
|
|
{
|
|
|
|
if let TopAlignLeft | TopAlignCenter | TopAlignRight = alignment {
|
|
|
|
y = other_y - self_height;
|
|
|
|
} else {
|
|
|
|
// bottom
|
|
|
|
y = other_y + other_height;
|
|
|
|
}
|
|
|
|
|
|
|
|
match alignment {
|
|
|
|
TopAlignLeft | BottomAlignLeft => x = other_x,
|
|
|
|
TopAlignCenter | BottomAlignCenter => {
|
|
|
|
x = other_x + (other_width - self_width) / 2;
|
|
|
|
}
|
|
|
|
TopAlignRight | BottomAlignRight => x = other_x + (other_width - self_width),
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let LeftAlignTop | LeftAlignCenter | LeftAlignBottom = alignment {
|
|
|
|
x = other_x - self_width;
|
|
|
|
} else {
|
|
|
|
x = other_x + other_width;
|
|
|
|
}
|
|
|
|
|
|
|
|
match alignment {
|
|
|
|
LeftAlignTop | RightAlignTop => y = other_y,
|
|
|
|
LeftAlignCenter | RightAlignCenter => {
|
|
|
|
y = other_y + (other_height - self_height) / 2;
|
|
|
|
}
|
|
|
|
LeftAlignBottom | RightAlignBottom => {
|
|
|
|
y = other_y + (other_height - self_height);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.set_location(Some(x), Some(y));
|
|
|
|
|
|
|
|
Some(())
|
2023-10-20 01:18:34 +02:00
|
|
|
};
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
attempt_set_loc();
|
|
|
|
}
|
|
|
|
|
2024-03-23 00:58:31 +01:00
|
|
|
/// Set this output's mode.
|
|
|
|
///
|
|
|
|
/// If `refresh_rate_millihertz` is provided, Pinnacle will attempt to use the mode with that
|
|
|
|
/// refresh rate. If it is not, Pinnacle will attempt to use the mode with the
|
|
|
|
/// highest refresh rate that matches the given size.
|
|
|
|
///
|
|
|
|
/// The refresh rate should be given in millihertz. For example, if you want a refresh rate of
|
|
|
|
/// 60Hz, use 60000.
|
|
|
|
///
|
|
|
|
/// If this output doesn't support the given mode, it will be ignored.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// output.get_focused()?.set_mode(2560, 1440, 144000);
|
|
|
|
/// ```
|
|
|
|
pub fn set_mode(
|
|
|
|
&self,
|
|
|
|
pixel_width: u32,
|
|
|
|
pixel_height: u32,
|
|
|
|
refresh_rate_millihertz: impl Into<Option<u32>>,
|
|
|
|
) {
|
|
|
|
let mut client = self.output_client.clone();
|
|
|
|
block_on_tokio(client.set_mode(SetModeRequest {
|
|
|
|
output_name: Some(self.name.clone()),
|
|
|
|
pixel_width: Some(pixel_width),
|
|
|
|
pixel_height: Some(pixel_height),
|
|
|
|
refresh_rate_millihz: refresh_rate_millihertz.into(),
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get all properties of this output.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use pinnacle_api::output::OutputProperties;
|
|
|
|
///
|
|
|
|
/// let OutputProperties {
|
|
|
|
/// make,
|
|
|
|
/// model,
|
|
|
|
/// x,
|
|
|
|
/// y,
|
|
|
|
/// pixel_width,
|
|
|
|
/// pixel_height,
|
|
|
|
/// refresh_rate,
|
|
|
|
/// physical_width,
|
|
|
|
/// physical_height,
|
|
|
|
/// focused,
|
|
|
|
/// tags,
|
|
|
|
/// } = output.get_focused()?.props();
|
|
|
|
/// ```
|
|
|
|
pub fn props(&self) -> OutputProperties {
|
2024-02-21 04:35:51 +01:00
|
|
|
block_on_tokio(self.props_async())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The async version of [`OutputHandle::props`].
|
|
|
|
pub async fn props_async(&self) -> OutputProperties {
|
|
|
|
let mut client = self.output_client.clone();
|
|
|
|
let response = client
|
|
|
|
.get_properties(output::v0alpha1::GetPropertiesRequest {
|
2024-01-22 06:45:09 +01:00
|
|
|
output_name: Some(self.name.clone()),
|
2024-02-21 04:35:51 +01:00
|
|
|
})
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.into_inner();
|
2024-01-22 06:45:09 +01:00
|
|
|
|
2024-02-23 23:24:43 +01:00
|
|
|
let tag = TAG.get().expect("TAG doesn't exist");
|
|
|
|
|
2023-10-20 01:18:34 +02:00
|
|
|
OutputProperties {
|
2024-01-22 06:45:09 +01:00
|
|
|
make: response.make,
|
|
|
|
model: response.model,
|
|
|
|
x: response.x,
|
|
|
|
y: response.y,
|
2024-03-23 00:37:27 +01:00
|
|
|
current_mode: response.current_mode.and_then(|mode| {
|
|
|
|
Some(Mode {
|
|
|
|
pixel_width: mode.pixel_width?,
|
|
|
|
pixel_height: mode.pixel_height?,
|
|
|
|
refresh_rate_millihertz: mode.refresh_rate_millihz?,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
preferred_mode: response.preferred_mode.and_then(|mode| {
|
|
|
|
Some(Mode {
|
|
|
|
pixel_width: mode.pixel_width?,
|
|
|
|
pixel_height: mode.pixel_height?,
|
|
|
|
refresh_rate_millihertz: mode.refresh_rate_millihz?,
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
modes: response
|
|
|
|
.modes
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|mode| {
|
|
|
|
Some(Mode {
|
|
|
|
pixel_width: mode.pixel_width?,
|
|
|
|
pixel_height: mode.pixel_height?,
|
|
|
|
refresh_rate_millihertz: mode.refresh_rate_millihz?,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect(),
|
2024-01-22 06:45:09 +01:00
|
|
|
physical_width: response.physical_width,
|
|
|
|
physical_height: response.physical_height,
|
|
|
|
focused: response.focused,
|
|
|
|
tags: response
|
|
|
|
.tag_ids
|
2023-10-20 02:26:12 +02:00
|
|
|
.into_iter()
|
2024-02-23 23:24:43 +01:00
|
|
|
.map(|id| tag.new_handle(id))
|
2023-10-20 02:26:12 +02:00
|
|
|
.collect(),
|
2023-10-20 01:18:34 +02:00
|
|
|
}
|
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
// TODO: make a macro for the following or something
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's make.
|
|
|
|
///
|
|
|
|
/// Shorthand for `self.props().make`.
|
|
|
|
pub fn make(&self) -> Option<String> {
|
|
|
|
self.props().make
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::make`].
|
|
|
|
pub async fn make_async(&self) -> Option<String> {
|
|
|
|
self.props_async().await.make
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's model.
|
2023-10-21 00:02:00 +02:00
|
|
|
///
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Shorthand for `self.props().make`.
|
|
|
|
pub fn model(&self) -> Option<String> {
|
|
|
|
self.props().model
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::model`].
|
|
|
|
pub async fn model_async(&self) -> Option<String> {
|
|
|
|
self.props_async().await.model
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's x position in the global space.
|
2023-10-21 00:02:00 +02:00
|
|
|
///
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Shorthand for `self.props().x`.
|
|
|
|
pub fn x(&self) -> Option<i32> {
|
|
|
|
self.props().x
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::x`].
|
|
|
|
pub async fn x_async(&self) -> Option<i32> {
|
|
|
|
self.props_async().await.x
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's y position in the global space.
|
2023-10-21 00:02:00 +02:00
|
|
|
///
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Shorthand for `self.props().y`.
|
|
|
|
pub fn y(&self) -> Option<i32> {
|
|
|
|
self.props().y
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::y`].
|
|
|
|
pub async fn y_async(&self) -> Option<i32> {
|
|
|
|
self.props_async().await.y
|
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Get this output's current mode.
|
2023-10-21 00:02:00 +02:00
|
|
|
///
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Shorthand for `self.props().current_mode`.
|
|
|
|
pub fn current_mode(&self) -> Option<Mode> {
|
|
|
|
self.props().current_mode
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The async version of [`OutputHandle::current_mode`].
|
|
|
|
pub async fn current_mode_async(&self) -> Option<Mode> {
|
|
|
|
self.props_async().await.current_mode
|
2024-02-21 04:35:51 +01:00
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Get this output's preferred mode.
|
2024-01-22 06:45:09 +01:00
|
|
|
///
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Shorthand for `self.props().preferred_mode`.
|
|
|
|
pub fn preferred_mode(&self) -> Option<Mode> {
|
|
|
|
self.props().preferred_mode
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The async version of [`OutputHandle::preferred_mode`].
|
|
|
|
pub async fn preferred_mode_async(&self) -> Option<Mode> {
|
|
|
|
self.props_async().await.preferred_mode
|
2024-02-21 04:35:51 +01:00
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Get all available modes this output supports.
|
2024-01-22 06:45:09 +01:00
|
|
|
///
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Shorthand for `self.props().modes`.
|
|
|
|
pub fn modes(&self) -> Vec<Mode> {
|
|
|
|
self.props().modes
|
2024-01-22 06:45:09 +01:00
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The async version of [`OutputHandle::modes`].
|
|
|
|
pub async fn modes_async(&self) -> Vec<Mode> {
|
|
|
|
self.props_async().await.modes
|
2024-02-21 04:35:51 +01:00
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's physical width in millimeters.
|
|
|
|
///
|
|
|
|
/// Shorthand for `self.props().physical_width`.
|
|
|
|
pub fn physical_width(&self) -> Option<u32> {
|
|
|
|
self.props().physical_width
|
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::physical_width`].
|
|
|
|
pub async fn physical_width_async(&self) -> Option<u32> {
|
|
|
|
self.props_async().await.physical_width
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's physical height in millimeters.
|
|
|
|
///
|
|
|
|
/// Shorthand for `self.props().physical_height`.
|
|
|
|
pub fn physical_height(&self) -> Option<u32> {
|
|
|
|
self.props().physical_height
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::physical_height`].
|
|
|
|
pub async fn physical_height_async(&self) -> Option<u32> {
|
|
|
|
self.props_async().await.physical_height
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get whether this output is focused or not.
|
|
|
|
///
|
|
|
|
/// This is currently implemented as the output with the most recent pointer motion.
|
|
|
|
///
|
|
|
|
/// Shorthand for `self.props().focused`.
|
|
|
|
pub fn focused(&self) -> Option<bool> {
|
|
|
|
self.props().focused
|
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::focused`].
|
|
|
|
pub async fn focused_async(&self) -> Option<bool> {
|
|
|
|
self.props_async().await.focused
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get the tags this output has.
|
|
|
|
///
|
|
|
|
/// Shorthand for `self.props().tags`
|
|
|
|
pub fn tags(&self) -> Vec<TagHandle> {
|
|
|
|
self.props().tags
|
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
|
2024-02-21 04:35:51 +01:00
|
|
|
/// The async version of [`OutputHandle::tags`].
|
|
|
|
pub async fn tags_async(&self) -> Vec<TagHandle> {
|
|
|
|
self.props_async().await.tags
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// Get this output's unique name (the name of its connector).
|
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
&self.name
|
|
|
|
}
|
2023-10-20 02:49:36 +02:00
|
|
|
}
|
|
|
|
|
2024-03-23 00:37:27 +01:00
|
|
|
/// A possible output pixel dimension and refresh rate configuration.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
|
|
|
|
pub struct Mode {
|
|
|
|
/// The width of the output, in pixels.
|
|
|
|
pub pixel_width: u32,
|
|
|
|
/// The height of the output, in pixels.
|
|
|
|
pub pixel_height: u32,
|
|
|
|
/// The output's refresh rate, in millihertz.
|
|
|
|
///
|
|
|
|
/// For example, 60Hz is returned as 60000.
|
|
|
|
pub refresh_rate_millihertz: u32,
|
|
|
|
}
|
|
|
|
|
2024-01-22 06:45:09 +01:00
|
|
|
/// The properties of an output.
|
2024-02-21 04:35:51 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
2024-01-22 06:45:09 +01:00
|
|
|
pub struct OutputProperties {
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The make of the output.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub make: Option<String>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The model of the output.
|
2024-01-22 06:45:09 +01:00
|
|
|
///
|
|
|
|
/// This is something like "27GL83A" or whatever crap monitor manufacturers name their monitors
|
|
|
|
/// these days.
|
|
|
|
pub model: Option<String>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The x position of the output in the global space.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub x: Option<i32>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The y position of the output in the global space.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub y: Option<i32>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The output's current mode.
|
|
|
|
pub current_mode: Option<Mode>,
|
|
|
|
/// The output's preferred mode.
|
|
|
|
pub preferred_mode: Option<Mode>,
|
|
|
|
/// All available modes the output supports.
|
|
|
|
pub modes: Vec<Mode>,
|
|
|
|
/// The output's physical width in millimeters.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub physical_width: Option<u32>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The output's physical height in millimeters.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub physical_height: Option<u32>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// Whether this output is focused or not.
|
2024-01-22 06:45:09 +01:00
|
|
|
///
|
|
|
|
/// This is currently implemented as the output with the most recent pointer motion.
|
|
|
|
pub focused: Option<bool>,
|
2024-03-23 00:37:27 +01:00
|
|
|
/// The tags this output has.
|
2024-01-22 06:45:09 +01:00
|
|
|
pub tags: Vec<TagHandle>,
|
2023-10-20 01:18:34 +02:00
|
|
|
}
|