Add basic API batching for Rust

Function coloring is fun
This commit is contained in:
Ottatop 2024-02-20 21:35:51 -06:00
parent 20af3a116c
commit b8c5ec751b
5 changed files with 496 additions and 140 deletions

View file

@ -21,15 +21,16 @@ use pinnacle_api_defs::pinnacle::{
};
use tonic::transport::Channel;
use crate::{block_on_tokio, tag::TagHandle};
use crate::{block_on_tokio, tag::TagHandle, util::Batch};
/// A struct that allows you to get handles to connected outputs and set them up.
///
/// See [`OutputHandle`] for more information.
#[derive(Debug, Clone)]
pub struct Output {
channel: Channel,
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
output_client: OutputServiceClient<Channel>,
tag_client: TagServiceClient<Channel>,
}
impl Output {
@ -38,19 +39,12 @@ impl Output {
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
) -> Self {
Self {
channel,
output_client: OutputServiceClient::new(channel.clone()),
tag_client: TagServiceClient::new(channel),
fut_sender,
}
}
fn create_output_client(&self) -> OutputServiceClient<Channel> {
OutputServiceClient::new(self.channel.clone())
}
fn create_tag_client(&self) -> TagServiceClient<Channel> {
TagServiceClient::new(self.channel.clone())
}
/// Get a handle to all connected outputs.
///
/// # Examples
@ -59,15 +53,22 @@ impl Output {
/// let outputs = output.get_all();
/// ```
pub fn get_all(&self) -> impl Iterator<Item = OutputHandle> {
let mut client = self.create_output_client();
let tag_client = self.create_tag_client();
block_on_tokio(client.get(output::v0alpha1::GetRequest {}))
block_on_tokio(self.get_all_async())
}
/// The async version of [`Output::get_all`].
pub async fn get_all_async(&self) -> impl Iterator<Item = OutputHandle> {
let mut client = self.output_client.clone();
let tag_client = self.tag_client.clone();
client
.get(output::v0alpha1::GetRequest {})
.await
.unwrap()
.into_inner()
.output_names
.into_iter()
.map(move |name| OutputHandle {
client: client.clone(),
output_client: client.clone(),
tag_client: tag_client.clone(),
name,
})
@ -84,8 +85,15 @@ impl Output {
/// let op2 = output.get_by_name("HDMI-2")?;
/// ```
pub fn get_by_name(&self, name: impl Into<String>) -> Option<OutputHandle> {
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> {
let name: String = name.into();
self.get_all().find(|output| output.name == name)
self.get_all_async()
.await
.find(|output| output.name == name)
}
/// Get a handle to the focused output.
@ -102,6 +110,14 @@ impl Output {
.find(|output| matches!(output.props().focused, Some(true)))
}
/// 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),
)
}
/// Connect a closure to be run on all current and future outputs.
///
/// When called, `connect_for_all` will do two things:
@ -126,8 +142,8 @@ impl Output {
for_all(output);
}
let mut client = self.create_output_client();
let tag_client = self.create_tag_client();
let mut client = self.output_client.clone();
let tag_client = self.tag_client.clone();
self.fut_sender
.unbounded_send(
@ -144,7 +160,7 @@ impl Output {
};
let output = OutputHandle {
client: client.clone(),
output_client: client.clone(),
tag_client: tag_client.clone(),
name: output_name,
};
@ -163,7 +179,7 @@ impl Output {
/// This allows you to manipulate outputs and get their properties.
#[derive(Clone, Debug)]
pub struct OutputHandle {
pub(crate) client: OutputServiceClient<Channel>,
pub(crate) output_client: OutputServiceClient<Channel>,
pub(crate) tag_client: TagServiceClient<Channel>,
pub(crate) name: String,
}
@ -245,7 +261,7 @@ impl OutputHandle {
/// // ^x=1920
/// ```
pub fn set_location(&self, x: impl Into<Option<i32>>, y: impl Into<Option<i32>>) {
let mut client = self.client.clone();
let mut client = self.output_client.clone();
block_on_tokio(client.set_location(SetLocationRequest {
output_name: Some(self.name.clone()),
x: x.into(),
@ -377,12 +393,17 @@ impl OutputHandle {
/// } = output.get_focused()?.props();
/// ```
pub fn props(&self) -> OutputProperties {
let mut client = self.client.clone();
let response = block_on_tokio(client.get_properties(
output::v0alpha1::GetPropertiesRequest {
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 {
output_name: Some(self.name.clone()),
},
))
})
.await
.unwrap()
.into_inner();
@ -401,8 +422,8 @@ impl OutputHandle {
.tag_ids
.into_iter()
.map(|id| TagHandle {
client: self.tag_client.clone(),
output_client: self.client.clone(),
tag_client: self.tag_client.clone(),
output_client: self.output_client.clone(),
id,
})
.collect(),
@ -418,6 +439,11 @@ impl OutputHandle {
self.props().make
}
/// The async version of [`OutputHandle::make`].
pub async fn make_async(&self) -> Option<String> {
self.props_async().await.make
}
/// Get this output's model.
///
/// Shorthand for `self.props().make`.
@ -425,6 +451,11 @@ impl OutputHandle {
self.props().model
}
/// The async version of [`OutputHandle::model`].
pub async fn model_async(&self) -> Option<String> {
self.props_async().await.model
}
/// Get this output's x position in the global space.
///
/// Shorthand for `self.props().x`.
@ -432,6 +463,11 @@ impl OutputHandle {
self.props().x
}
/// The async version of [`OutputHandle::x`].
pub async fn x_async(&self) -> Option<i32> {
self.props_async().await.x
}
/// Get this output's y position in the global space.
///
/// Shorthand for `self.props().y`.
@ -439,6 +475,11 @@ impl OutputHandle {
self.props().y
}
/// The async version of [`OutputHandle::y`].
pub async fn y_async(&self) -> Option<i32> {
self.props_async().await.y
}
/// Get this output's screen width in pixels.
///
/// Shorthand for `self.props().pixel_width`.
@ -446,6 +487,11 @@ impl OutputHandle {
self.props().pixel_width
}
/// The async version of [`OutputHandle::pixel_width`].
pub async fn pixel_width_async(&self) -> Option<u32> {
self.props_async().await.pixel_width
}
/// Get this output's screen height in pixels.
///
/// Shorthand for `self.props().pixel_height`.
@ -453,6 +499,11 @@ impl OutputHandle {
self.props().pixel_height
}
/// The async version of [`OutputHandle::pixel_height`].
pub async fn pixel_height_async(&self) -> Option<u32> {
self.props_async().await.pixel_height
}
/// Get this output's refresh rate in millihertz.
///
/// For example, 144Hz will be returned as 144000.
@ -462,6 +513,11 @@ impl OutputHandle {
self.props().refresh_rate
}
/// The async version of [`OutputHandle::refresh_rate`].
pub async fn refresh_rate_async(&self) -> Option<u32> {
self.props_async().await.refresh_rate
}
/// Get this output's physical width in millimeters.
///
/// Shorthand for `self.props().physical_width`.
@ -469,6 +525,11 @@ impl OutputHandle {
self.props().physical_width
}
/// The async version of [`OutputHandle::physical_width`].
pub async fn physical_width_async(&self) -> Option<u32> {
self.props_async().await.physical_width
}
/// Get this output's physical height in millimeters.
///
/// Shorthand for `self.props().physical_height`.
@ -476,6 +537,11 @@ impl OutputHandle {
self.props().physical_height
}
/// The async version of [`OutputHandle::physical_height`].
pub async fn physical_height_async(&self) -> Option<u32> {
self.props_async().await.physical_height
}
/// Get whether this output is focused or not.
///
/// This is currently implemented as the output with the most recent pointer motion.
@ -485,6 +551,11 @@ impl OutputHandle {
self.props().focused
}
/// The async version of [`OutputHandle::focused`].
pub async fn focused_async(&self) -> Option<bool> {
self.props_async().await.focused
}
/// Get the tags this output has.
///
/// Shorthand for `self.props().tags`
@ -492,6 +563,11 @@ impl OutputHandle {
self.props().tags
}
/// The async version of [`OutputHandle::tags`].
pub async fn tags_async(&self) -> Vec<TagHandle> {
self.props_async().await.tags
}
/// Get this output's unique name (the name of its connector).
pub fn name(&self) -> &str {
&self.name
@ -499,7 +575,7 @@ impl OutputHandle {
}
/// The properties of an output.
#[derive(Clone, Debug)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct OutputProperties {
/// The make of the output
pub make: Option<String>,

View file

@ -24,6 +24,7 @@ pub struct Process {
}
/// Optional callbacks to be run when a spawned process prints to stdout or stderr or exits.
#[derive(Default)]
pub struct SpawnCallbacks {
/// A callback that will be run when a process prints to stdout with a line
pub stdout: Option<Box<dyn FnMut(String) + Send>>,

View file

@ -34,7 +34,7 @@ use std::{
sync::{Arc, Mutex},
};
use futures::{channel::mpsc::UnboundedSender, future::BoxFuture};
use futures::{channel::mpsc::UnboundedSender, future::BoxFuture, FutureExt};
use num_enum::TryFromPrimitive;
use pinnacle_api_defs::pinnacle::{
output::v0alpha1::output_service_client::OutputServiceClient,
@ -51,6 +51,7 @@ use tonic::transport::Channel;
use crate::{
block_on_tokio,
output::{Output, OutputHandle},
util::Batch,
};
/// A struct that allows you to add and remove tags and get [`TagHandle`]s.
@ -58,6 +59,8 @@ use crate::{
pub struct Tag {
channel: Channel,
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
tag_client: TagServiceClient<Channel>,
output_client: OutputServiceClient<Channel>,
}
impl Tag {
@ -66,19 +69,13 @@ impl Tag {
fut_sender: UnboundedSender<BoxFuture<'static, ()>>,
) -> Self {
Self {
tag_client: TagServiceClient::new(channel.clone()),
output_client: OutputServiceClient::new(channel.clone()),
channel,
fut_sender,
}
}
fn create_tag_client(&self) -> TagServiceClient<Channel> {
TagServiceClient::new(self.channel.clone())
}
fn create_output_client(&self) -> OutputServiceClient<Channel> {
OutputServiceClient::new(self.channel.clone())
}
/// Add tags to the specified output.
///
/// This will add tags with the given names to `output` and return [`TagHandle`]s to all of
@ -97,20 +94,31 @@ impl Tag {
output: &OutputHandle,
tag_names: impl IntoIterator<Item = impl Into<String>>,
) -> impl Iterator<Item = TagHandle> {
let mut client = self.create_tag_client();
let output_client = self.create_output_client();
block_on_tokio(self.add_async(output, tag_names))
}
/// The async version of [`Tag::add`].
pub async fn add_async(
&self,
output: &OutputHandle,
tag_names: impl IntoIterator<Item = impl Into<String>>,
) -> impl Iterator<Item = TagHandle> {
let mut client = self.tag_client.clone();
let output_client = self.output_client.clone();
let tag_names = tag_names.into_iter().map(Into::into).collect();
let response = block_on_tokio(client.add(AddRequest {
let response = client
.add(AddRequest {
output_name: Some(output.name.clone()),
tag_names,
}))
})
.await
.unwrap()
.into_inner();
response.tag_ids.into_iter().map(move |id| TagHandle {
client: client.clone(),
tag_client: client.clone(),
output_client: output_client.clone(),
id,
})
@ -124,15 +132,22 @@ impl Tag {
/// let all_tags = tag.get_all();
/// ```
pub fn get_all(&self) -> impl Iterator<Item = TagHandle> {
let mut client = self.create_tag_client();
let output_client = self.create_output_client();
block_on_tokio(self.get_all_async())
}
let response = block_on_tokio(client.get(tag::v0alpha1::GetRequest {}))
/// The async version of [`Tag::get_all`].
pub async fn get_all_async(&self) -> impl Iterator<Item = TagHandle> {
let mut client = self.tag_client.clone();
let output_client = self.output_client.clone();
let response = client
.get(tag::v0alpha1::GetRequest {})
.await
.unwrap()
.into_inner();
response.tag_ids.into_iter().map(move |id| TagHandle {
client: client.clone(),
tag_client: client.clone(),
output_client: output_client.clone(),
id,
})
@ -149,18 +164,20 @@ impl Tag {
/// let tg = tag.get("Thing");
/// ```
pub fn get(&self, name: impl Into<String>) -> Option<TagHandle> {
block_on_tokio(self.get_async(name))
}
/// The async version of [`Tag::get`].
pub async fn get_async(&self, name: impl Into<String>) -> Option<TagHandle> {
let name = name.into();
let output_module = Output::new(self.channel.clone(), self.fut_sender.clone());
let focused_output = output_module.get_focused();
self.get_all().find(|tag| {
let props = tag.props();
let same_tag_name = props.name.as_ref() == Some(&name);
let same_output = props.output.is_some_and(|op| Some(op) == focused_output);
same_tag_name && same_output
})
if let Some(output) = focused_output {
self.get_on_output_async(name, &output).await
} else {
None
}
}
/// Get a handle to the first tag with the given name on the specified output.
@ -177,17 +194,27 @@ impl Tag {
&self,
name: impl Into<String>,
output: &OutputHandle,
) -> Option<TagHandle> {
block_on_tokio(self.get_on_output_async(name, output))
}
/// The async version of [`Tag::get_on_output`].
pub async fn get_on_output_async(
&self,
name: impl Into<String>,
output: &OutputHandle,
) -> Option<TagHandle> {
let name = name.into();
self.get_all().find(|tag| {
let props = tag.props();
self.get_all_async().await.batch_find(
|tag| tag.props_async().boxed(),
|props| {
let same_tag_name = props.name.as_ref() == Some(&name);
let same_output = props.output.is_some_and(|op| &op == output);
let same_output = props.output.as_ref().is_some_and(|op| op == output);
same_tag_name && same_output
})
},
)
}
/// Remove the given tags from their outputs.
@ -202,7 +229,7 @@ impl Tag {
pub fn remove(&self, tags: impl IntoIterator<Item = TagHandle>) {
let tag_ids = tags.into_iter().map(|handle| handle.id).collect::<Vec<_>>();
let mut client = self.create_tag_client();
let mut client = self.tag_client.clone();
block_on_tokio(client.remove(RemoveRequest { tag_ids })).unwrap();
}
@ -333,9 +360,9 @@ pub struct LayoutCycler {
/// This handle allows you to do things like switch to tags and get their properties.
#[derive(Debug, Clone)]
pub struct TagHandle {
pub(crate) client: TagServiceClient<Channel>,
pub(crate) output_client: OutputServiceClient<Channel>,
pub(crate) id: u32,
pub(crate) tag_client: TagServiceClient<Channel>,
pub(crate) output_client: OutputServiceClient<Channel>,
}
impl PartialEq for TagHandle {
@ -388,7 +415,7 @@ impl TagHandle {
/// tag.get("3")?.switch_to(); // Displays Steam
/// ```
pub fn switch_to(&self) {
let mut client = self.client.clone();
let mut client = self.tag_client.clone();
block_on_tokio(client.switch_to(SwitchToRequest {
tag_id: Some(self.id),
}))
@ -414,7 +441,7 @@ impl TagHandle {
/// tag.get("2")?.set_active(false); // Displays Steam
/// ```
pub fn set_active(&self, set: bool) {
let mut client = self.client.clone();
let mut client = self.tag_client.clone();
block_on_tokio(client.set_active(SetActiveRequest {
tag_id: Some(self.id),
set_or_toggle: Some(tag::v0alpha1::set_active_request::SetOrToggle::Set(set)),
@ -442,7 +469,7 @@ impl TagHandle {
/// tag.get("2")?.toggle(); // Displays nothing
/// ```
pub fn toggle_active(&self) {
let mut client = self.client.clone();
let mut client = self.tag_client.clone();
block_on_tokio(client.set_active(SetActiveRequest {
tag_id: Some(self.id),
set_or_toggle: Some(tag::v0alpha1::set_active_request::SetOrToggle::Toggle(())),
@ -463,8 +490,9 @@ impl TagHandle {
/// tags[3].remove();
/// // "DP-1" now only has tags "1" and "Buckle"
/// ```
pub fn remove(mut self) {
block_on_tokio(self.client.remove(RemoveRequest {
pub fn remove(&self) {
let mut tag_client = self.tag_client.clone();
block_on_tokio(tag_client.remove(RemoveRequest {
tag_ids: vec![self.id],
}))
.unwrap();
@ -487,7 +515,7 @@ impl TagHandle {
/// tag.get("1", None)?.set_layout(Layout::CornerTopLeft);
/// ```
pub fn set_layout(&self, layout: Layout) {
let mut client = self.client.clone();
let mut client = self.tag_client.clone();
block_on_tokio(client.set_layout(SetLayoutRequest {
tag_id: Some(self.id),
layout: Some(layout as i32),
@ -509,12 +537,19 @@ impl TagHandle {
/// } = tag.get("1", None)?.props();
/// ```
pub fn props(&self) -> TagProperties {
let mut client = self.client.clone();
block_on_tokio(self.props_async())
}
/// The async version of [`TagHandle::props`].
pub async fn props_async(&self) -> TagProperties {
let mut client = self.tag_client.clone();
let output_client = self.output_client.clone();
let response = block_on_tokio(client.get_properties(tag::v0alpha1::GetPropertiesRequest {
let response = client
.get_properties(tag::v0alpha1::GetPropertiesRequest {
tag_id: Some(self.id),
}))
})
.await
.unwrap()
.into_inner();
@ -522,7 +557,7 @@ impl TagHandle {
active: response.active,
name: response.name,
output: response.output_name.map(|name| OutputHandle {
client: output_client,
output_client,
tag_client: client,
name,
}),
@ -536,6 +571,11 @@ impl TagHandle {
self.props().active
}
/// The async version of [`TagHandle::active`].
pub async fn active_async(&self) -> Option<bool> {
self.props_async().await.active
}
/// Get this tag's name.
///
/// Shorthand for `self.props().name`.
@ -543,15 +583,26 @@ impl TagHandle {
self.props().name
}
/// The async version of [`TagHandle::name`].
pub async fn name_async(&self) -> Option<String> {
self.props_async().await.name
}
/// Get a handle to the output this tag is on.
///
/// Shorthand for `self.props().output`.
pub fn output(&self) -> Option<OutputHandle> {
self.props().output
}
/// The async version of [`TagHandle::output`].
pub async fn output_async(&self) -> Option<OutputHandle> {
self.props_async().await.output
}
}
/// Properties of a tag.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct TagProperties {
/// Whether the tag is active or not
pub active: Option<bool>,

View file

@ -4,6 +4,15 @@
//! Utility types.
use std::pin::Pin;
use futures::{stream::FuturesOrdered, Future, StreamExt};
use crate::block_on_tokio;
pub use crate::batch_boxed;
pub use crate::batch_boxed_async;
/// The size and location of something.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Geometry {
@ -16,3 +25,160 @@ pub struct Geometry {
/// The height
pub height: u32,
}
/// Batch a set of requests that will be sent ot the compositor all at once.
///
/// # Rationale
///
/// Normally, all API calls are blocking. For example, calling [`Window::get_all`][crate::window::Window::get_all]
/// then calling [`WindowHandle.props`][crate::window::WindowHandle::props]
/// on each returned window handle will block after each `props` call waiting for the compositor to respond:
///
/// ```
/// // This will block after each call to `window.props()`, meaning this all happens synchronously.
/// // If the compositor is running slowly for whatever reason, this will take a long time to complete.
/// let props = window.get_all()
/// .map(|window| window.props())
/// .collect::<Vec<_>>();
/// ```
///
/// In order to mitigate this issue, you can batch up a set of API calls using this function.
/// This will send all requests to the compositor at once without blocking, then wait for the compositor
/// to respond.
///
/// You'll see that this function takes in an `IntoIterator` of `Future`s. As such,
/// all API calls that return something have an async variant named `*_async` that returns a future.
/// You must pass these futures into the batch function instead of their non-async counterparts.
///
/// # The `batch_boxed` macro
/// The [`util`][crate::util] module also provides the [`batch_boxed`] macro.
///
/// The [`batch`] function only accepts one concrete type of future, meaning that you
/// can only batch a collection of futures from one specific function or method.
///
/// As a convenience, `batch_boxed` accepts one or more different futures that return the same type.
/// It will place provided futures in a `Pin<Box<_>>` to erase the types and pass them along to `batch`.
///
/// # Examples
///
/// ```
/// use pinnacle_api::util::batch;
/// use pinnacle_api::window::WindowProperties;
///
/// let props: Vec<WindowProperties> = batch(window.get_all().map(|window| window.props_async()));
/// // Don't forget the `async` ^^^^^
/// ```
///
pub fn batch<T>(requests: impl IntoIterator<Item = impl Future<Output = T>>) -> Vec<T> {
let results = FuturesOrdered::from_iter(requests).collect::<Vec<_>>();
block_on_tokio(results)
}
/// The async version of [`batch`].
///
/// See [`batch`] for more information.
pub async fn batch_async<T>(requests: impl IntoIterator<Item = impl Future<Output = T>>) -> Vec<T> {
let results = FuturesOrdered::from_iter(requests).collect::<Vec<_>>();
results.await
}
/// A convenience macro to batch API calls in different concrete futures.
///
/// The [`batch`] function only accepts a collection of the same concrete future e.g.
/// from a single async function or method.
///
/// To support different futures (that still return the same value), this macro will place provided
/// futures in a `Pin<Box<_>>` to erase their type and pass them along to `batch`.
///
/// # Examples
/// ```
/// use pinnacle_api::util::batch_boxed;
///
/// let windows = window.get_all();
/// let first = windows.next()?;
/// let last = windows.last()?;
///
/// let classes: Vec<String> = batch_boxed![
/// async {
/// let mut class = first.class_async().await;
/// class.unwrap_or("no class".to_string())
/// },
/// async {
/// last.class_async().await
/// },
/// ];
/// ```
#[macro_export]
macro_rules! batch_boxed {
[ $first:expr, $($request:expr),* ] => {
$crate::util::batch([
::std::boxed::Box::pin($first) as ::std::pin::Pin<::std::boxed::Box<dyn std::future::Future<Output = _>>>,
$(
::std::boxed::Box::pin($request),
)*
])
};
}
/// The async version of [`batch_boxed`].
///
/// See [`batch_boxed`] for more information.
#[macro_export]
macro_rules! batch_boxed_async {
[ $first:expr, $($request:expr),* ] => {
$crate::util::batch_async([
::std::boxed::Box::pin($first) as ::std::pin::Pin<::std::boxed::Box<dyn std::future::Future<Output = _>>>,
$(
::std::boxed::Box::pin($request),
)*
])
};
}
/// Methods for batch sending API requests to the compositor.
pub trait Batch<I> {
/// [`batch_map`][Batch::batch_map]s then finds the object for which `f` with the results
/// returns `true`.
fn batch_find<M, F, FutOp>(self, map_to_future: M, find: F) -> Option<I>
where
Self: Sized,
M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
F: FnMut(&FutOp) -> bool;
/// Maps the collection to compositor requests, batching all calls.
fn batch_map<F, FutOp>(self, map: F) -> impl Iterator<Item = FutOp>
where
Self: Sized,
F: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>;
}
impl<T: IntoIterator<Item = I>, I> Batch<I> for T {
fn batch_find<M, F, FutOp>(self, map_to_future: M, mut find: F) -> Option<I>
where
Self: Sized,
M: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
F: FnMut(&FutOp) -> bool,
{
let items = self.into_iter().collect::<Vec<_>>();
let futures = items.iter().map(map_to_future);
let results = crate::util::batch(futures);
assert_eq!(items.len(), results.len());
items
.into_iter()
.zip(results)
.find(|(_, fut_op)| find(fut_op))
.map(|(item, _)| item)
}
fn batch_map<F, FutOp>(self, map: F) -> impl Iterator<Item = FutOp>
where
Self: Sized,
F: for<'a> FnMut(&'a I) -> Pin<Box<dyn Future<Output = FutOp> + 'a>>,
{
let items = self.into_iter().collect::<Vec<_>>();
let futures = items.iter().map(map);
crate::util::batch(futures).into_iter()
}
}

View file

@ -12,6 +12,7 @@
//!
//! This module also allows you to set window rules; see the [rules] module for more information.
use futures::FutureExt;
use num_enum::TryFromPrimitive;
use pinnacle_api_defs::pinnacle::{
output::v0alpha1::output_service_client::OutputServiceClient,
@ -30,7 +31,12 @@ use pinnacle_api_defs::pinnacle::{
};
use tonic::transport::Channel;
use crate::{block_on_tokio, input::MouseButton, tag::TagHandle, util::Geometry};
use crate::{
block_on_tokio,
input::MouseButton,
tag::TagHandle,
util::{Batch, Geometry},
};
use self::rules::{WindowRule, WindowRuleCondition};
@ -41,24 +47,18 @@ pub mod rules;
/// See [`WindowHandle`] for more information.
#[derive(Debug, Clone)]
pub struct Window {
channel: Channel,
window_client: WindowServiceClient<Channel>,
tag_client: TagServiceClient<Channel>,
output_client: OutputServiceClient<Channel>,
}
impl Window {
pub(crate) fn new(channel: Channel) -> Self {
Self { channel }
Self {
window_client: WindowServiceClient::new(channel.clone()),
tag_client: TagServiceClient::new(channel.clone()),
output_client: OutputServiceClient::new(channel),
}
fn create_window_client(&self) -> WindowServiceClient<Channel> {
WindowServiceClient::new(self.channel.clone())
}
fn create_tag_client(&self) -> TagServiceClient<Channel> {
TagServiceClient::new(self.channel.clone())
}
fn create_output_client(&self) -> OutputServiceClient<Channel> {
OutputServiceClient::new(self.channel.clone())
}
/// Start moving the window with the mouse.
@ -66,7 +66,7 @@ impl Window {
/// This will begin moving the window under the pointer using the specified [`MouseButton`].
/// The button must be held down at the time this method is called for the move to start.
///
/// This is intended to be used with [`Input::keybind`][crate::input::Input::keybind].
/// This is intended to be used with [`Input::mousebind`][crate::input::Input::mousebind].
///
/// # Examples
///
@ -79,11 +79,12 @@ impl Window {
/// });
/// ```
pub fn begin_move(&self, button: MouseButton) {
let mut client = self.create_window_client();
block_on_tokio(client.move_grab(MoveGrabRequest {
let mut client = self.window_client.clone();
if let Err(status) = block_on_tokio(client.move_grab(MoveGrabRequest {
button: Some(button as u32),
}))
.unwrap();
})) {
eprintln!("ERROR: {status}");
}
}
/// Start resizing the window with the mouse.
@ -91,7 +92,7 @@ impl Window {
/// This will begin resizing the window under the pointer using the specified [`MouseButton`].
/// The button must be held down at the time this method is called for the resize to start.
///
/// This is intended to be used with [`Input::keybind`][crate::input::Input::keybind].
/// This is intended to be used with [`Input::mousebind`][crate::input::Input::mousebind].
///
/// # Examples
///
@ -104,7 +105,7 @@ impl Window {
/// });
/// ```
pub fn begin_resize(&self, button: MouseButton) {
let mut client = self.create_window_client();
let mut client = self.window_client.clone();
block_on_tokio(client.resize_grab(ResizeGrabRequest {
button: Some(button as u32),
}))
@ -119,16 +120,23 @@ impl Window {
/// let windows = window.get_all();
/// ```
pub fn get_all(&self) -> impl Iterator<Item = WindowHandle> {
let mut client = self.create_window_client();
let tag_client = self.create_tag_client();
let output_client = self.create_output_client();
block_on_tokio(client.get(GetRequest {}))
block_on_tokio(self.get_all_async())
}
/// The async version of [`Window::get_all`].
pub async fn get_all_async(&self) -> impl Iterator<Item = WindowHandle> {
let mut client = self.window_client.clone();
let tag_client = self.tag_client.clone();
let output_client = self.output_client.clone();
client
.get(GetRequest {})
.await
.unwrap()
.into_inner()
.window_ids
.into_iter()
.map(move |id| WindowHandle {
client: client.clone(),
window_client: client.clone(),
id,
tag_client: tag_client.clone(),
output_client: output_client.clone(),
@ -143,8 +151,15 @@ impl Window {
/// let focused_window = window.get_focused()?;
/// ```
pub fn get_focused(&self) -> Option<WindowHandle> {
self.get_all()
.find(|window| matches!(window.props().focused, Some(true)))
block_on_tokio(self.get_focused_async())
}
/// The async version of [`Window::get_focused`].
pub async fn get_focused_async(&self) -> Option<WindowHandle> {
self.get_all_async().await.batch_find(
|win| win.focused_async().boxed(),
|focused| focused.is_some_and(|focused| focused),
)
}
/// Add a window rule.
@ -154,7 +169,7 @@ impl Window {
///
/// TODO:
pub fn add_window_rule(&self, cond: WindowRuleCondition, rule: WindowRule) {
let mut client = self.create_window_client();
let mut client = self.window_client.clone();
block_on_tokio(client.add_window_rule(AddWindowRuleRequest {
cond: Some(cond.0),
@ -169,10 +184,10 @@ impl Window {
/// This allows you to manipulate the window and get its properties.
#[derive(Debug, Clone)]
pub struct WindowHandle {
pub(crate) client: WindowServiceClient<Channel>,
pub(crate) id: u32,
pub(crate) tag_client: TagServiceClient<Channel>,
pub(crate) output_client: OutputServiceClient<Channel>,
id: u32,
window_client: WindowServiceClient<Channel>,
tag_client: TagServiceClient<Channel>,
output_client: OutputServiceClient<Channel>,
}
impl PartialEq for WindowHandle {
@ -202,7 +217,7 @@ pub enum FullscreenOrMaximized {
}
/// Properties of a window.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct WindowProperties {
/// The location and size of the window
pub geometry: Option<Geometry>,
@ -215,7 +230,7 @@ pub struct WindowProperties {
/// Whether the window is floating or not
///
/// Note that a window can still be floating even if it's fullscreen or maximized; those two
/// state will just override the floating state.
/// states will just override the floating state.
pub floating: Option<bool>,
/// Whether the window is fullscreen, maximized, or neither
pub fullscreen_or_maximized: Option<FullscreenOrMaximized>,
@ -234,8 +249,9 @@ impl WindowHandle {
/// // Close the focused window
/// window.get_focused()?.close()
/// ```
pub fn close(mut self) {
block_on_tokio(self.client.close(CloseRequest {
pub fn close(&self) {
let mut window_client = self.window_client.clone();
block_on_tokio(window_client.close(CloseRequest {
window_id: Some(self.id),
}))
.unwrap();
@ -252,7 +268,7 @@ impl WindowHandle {
/// window.get_focused()?.set_fullscreen(true);
/// ```
pub fn set_fullscreen(&self, set: bool) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_fullscreen(SetFullscreenRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Set(
@ -273,7 +289,7 @@ impl WindowHandle {
/// window.get_focused()?.toggle_fullscreen();
/// ```
pub fn toggle_fullscreen(&self) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_fullscreen(SetFullscreenRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(())),
@ -292,7 +308,7 @@ impl WindowHandle {
/// window.get_focused()?.set_maximized(true);
/// ```
pub fn set_maximized(&self, set: bool) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_maximized(SetMaximizedRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_maximized_request::SetOrToggle::Set(
@ -304,7 +320,7 @@ impl WindowHandle {
/// Toggle this window between maximized and not.
///
/// If it is fullscreen, setting it to maximized will remove the fullscreen state.
/// If it is fullscreen, toggling it to maximized will remove the fullscreen state.
///
/// # Examples
///
@ -313,7 +329,7 @@ impl WindowHandle {
/// window.get_focused()?.toggle_maximized();
/// ```
pub fn toggle_maximized(&self) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_maximized(SetMaximizedRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(())),
@ -335,7 +351,7 @@ impl WindowHandle {
/// window.get_focused()?.set_floating(true);
/// ```
pub fn set_floating(&self, set: bool) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_floating(SetFloatingRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_floating_request::SetOrToggle::Set(
@ -359,7 +375,7 @@ impl WindowHandle {
/// window.get_focused()?.toggle_floating();
/// ```
pub fn toggle_floating(&self) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_floating(SetFloatingRequest {
window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_floating_request::SetOrToggle::Toggle(
@ -381,7 +397,7 @@ impl WindowHandle {
/// window.get_focused()?.move_to_tag(&tag.get("Code", None)?);
/// ```
pub fn move_to_tag(&self, tag: &TagHandle) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.move_to_tag(MoveToTagRequest {
window_id: Some(self.id),
@ -402,7 +418,7 @@ impl WindowHandle {
/// focused.set_tag(&tg, false); // `focused` no longer has tag "Potato"
/// ```
pub fn set_tag(&self, tag: &TagHandle, set: bool) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_tag(SetTagRequest {
window_id: Some(self.id),
@ -426,7 +442,7 @@ impl WindowHandle {
/// focused.toggle_tag(&tg); // `focused` no longer has tag "Potato"
/// ```
pub fn toggle_tag(&self, tag: &TagHandle) {
let mut client = self.client.clone();
let mut client = self.window_client.clone();
block_on_tokio(client.set_tag(SetTagRequest {
window_id: Some(self.id),
@ -454,15 +470,26 @@ impl WindowHandle {
/// } = window.get_focused()?.props();
/// ```
pub fn props(&self) -> WindowProperties {
let mut client = self.client.clone();
block_on_tokio(self.props_async())
}
/// The async version of [`props`][Self::props].
pub async fn props_async(&self) -> WindowProperties {
let mut client = self.window_client.clone();
let tag_client = self.tag_client.clone();
let response = block_on_tokio(client.get_properties(
window::v0alpha1::GetPropertiesRequest {
let response = match client
.get_properties(window::v0alpha1::GetPropertiesRequest {
window_id: Some(self.id),
},
))
.unwrap()
.into_inner();
})
.await
{
Ok(response) => response.into_inner(),
Err(status) => {
eprintln!("ERROR: {status}");
return WindowProperties::default();
}
};
let fullscreen_or_maximized = response
.fullscreen_or_maximized
@ -488,7 +515,7 @@ impl WindowHandle {
.tag_ids
.into_iter()
.map(|id| TagHandle {
client: tag_client.clone(),
tag_client: tag_client.clone(),
output_client: self.output_client.clone(),
id,
})
@ -503,6 +530,11 @@ impl WindowHandle {
self.props().geometry
}
/// The async version of [`geometry`][Self::geometry].
pub async fn geometry_async(&self) -> Option<Geometry> {
self.props_async().await.geometry
}
/// Get this window's class.
///
/// Shorthand for `self.props().class`.
@ -510,6 +542,11 @@ impl WindowHandle {
self.props().class
}
/// The async version of [`class`][Self::class].
pub async fn class_async(&self) -> Option<String> {
self.props_async().await.class
}
/// Get this window's title.
///
/// Shorthand for `self.props().title`.
@ -517,6 +554,11 @@ impl WindowHandle {
self.props().title
}
/// The async version of [`title`][Self::title].
pub async fn title_async(&self) -> Option<String> {
self.props_async().await.title
}
/// Get whether or not this window is focused.
///
/// Shorthand for `self.props().focused`.
@ -524,6 +566,11 @@ impl WindowHandle {
self.props().focused
}
/// The async version of [`focused`][Self::focused].
pub async fn focused_async(&self) -> Option<bool> {
self.props_async().await.focused
}
/// Get whether or not this window is floating.
///
/// Shorthand for `self.props().floating`.
@ -531,6 +578,11 @@ impl WindowHandle {
self.props().floating
}
/// The async version of [`floating`][Self::floating]
pub async fn floating_async(&self) -> Option<bool> {
self.props_async().await.floating
}
/// Get whether this window is fullscreen, maximized, or neither.
///
/// Shorthand for `self.props().fullscreen_or_maximized`.
@ -538,10 +590,20 @@ impl WindowHandle {
self.props().fullscreen_or_maximized
}
/// The async version of [`fullscreen_or_maximized`][Self::fullscreen_or_maximized].
pub async fn fullscreen_or_maximized_async(&self) -> Option<FullscreenOrMaximized> {
self.props_async().await.fullscreen_or_maximized
}
/// Get all the tags on this window.
///
/// Shorthand for `self.props().tags`.
pub fn tags(&self) -> Vec<TagHandle> {
self.props().tags
}
/// The async version of [`tags`][Self::tags].
pub async fn tags_async(&self) -> Vec<TagHandle> {
self.props_async().await.tags
}
}