pinnacle_api/lib.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#![warn(missing_docs)]
6
7//! The Rust implementation of [Pinnacle](https://github.com/pinnacle-comp/pinnacle)'s
8//! configuration API.
9//!
10//! This library allows you to interface with the Pinnacle compositor and configure various aspects
11//! like input and the tag system.
12//!
13//! # Configuration
14//!
15//! ## With the config generation CLI
16//!
17//! To create a Rust config using the config generation CLI, run
18//!
19//! ```sh
20//! pinnacle config gen
21//! ```
22//!
23//! and step through the interactive generator (be sure to select Rust as the language).
24//! This will create the default config in the specified directory.
25//!
26//! ## Manually
27//!
28//! ### 1. Create a Cargo project
29//!
30//! Create a Cargo project in your config directory with `cargo init`.
31//!
32//! ### 2. Create `pinnacle.toml`
33//!
34//! `pinnacle.toml` is what tells Pinnacle what command is used to start the config.
35//!
36//! Create `pinnacle.toml` at the root of the cargo project and add the following to it:
37//! ```toml
38//! run = ["cargo", "run"]
39//! ```
40//!
41//! Pinnacle will now use `cargo run` to start your config.
42//!
43//! ## 3. Set up dependencies
44//!
45//! In your `Cargo.toml`, add `pinnacle-api` as a dependency:
46//!
47//! ```toml
48//! [dependencies]
49//! pinnacle-api = { git = "https://github.com/pinnacle-comp/pinnacle" }
50//! ```
51//!
52//! ## 4. Set up the main function
53//!
54//! In `main.rs`, remove the main function and create an `async` one. This is where your config
55//! will start from. Then, call the [`main`] macro, which will create a `tokio` main function
56//! that will perform the necessary setup and call your async function.
57//!
58//! ```
59//! async fn config() {
60//! // Your config here
61//! }
62//!
63//! pinnacle_api::main!(config);
64//! ```
65//!
66//! ## 5. Begin crafting your config!
67//!
68//! Take a look at the default config or browse the docs to see what you can do.
69
70use client::Client;
71use futures::{Future, StreamExt};
72use hyper_util::rt::TokioIo;
73use tonic::transport::{Endpoint, Uri};
74use tower::service_fn;
75
76pub mod debug;
77pub mod experimental;
78pub mod input;
79pub mod layout;
80pub mod output;
81pub mod pinnacle;
82pub mod process;
83pub mod render;
84pub mod signal;
85#[cfg(feature = "snowcap")]
86pub mod snowcap;
87pub mod tag;
88pub mod util;
89pub mod window;
90
91mod client;
92
93pub use tokio;
94pub use xkbcommon::xkb::Keysym;
95
96const SOCKET_PATH: &str = "PINNACLE_GRPC_SOCKET";
97
98/// Connects to Pinnacle.
99///
100/// This function is called by the [`main`] and [`config`] macros.
101/// You'll only need to use this if you aren't using them.
102pub async fn connect() -> Result<(), Box<dyn std::error::Error>> {
103 // port doesn't matter, we use a unix socket
104 let channel = Endpoint::try_from("http://[::]:50051")?
105 .connect_with_connector(service_fn(|_: Uri| async {
106 let path = std::env::var(SOCKET_PATH)
107 .expect("PINNACLE_GRPC_SOCKET was not set; is Pinnacle running?");
108
109 Ok::<_, std::io::Error>(TokioIo::new(tokio::net::UnixStream::connect(path).await?))
110 }))
111 .await
112 .unwrap();
113
114 let socket_path = std::env::var(SOCKET_PATH).unwrap();
115 println!("Connected to {socket_path}");
116
117 Client::init(channel.clone());
118
119 #[cfg(feature = "snowcap")]
120 snowcap_api::connect().await.unwrap();
121
122 Ok(())
123}
124
125/// Blocks until Pinnacle exits.
126///
127/// This function is called by the [`main`] and [`config`] macros.
128/// You'll only need to use this if you aren't using them.
129pub async fn block() {
130 let (_sender, mut keepalive_stream) = crate::pinnacle::keepalive().await;
131
132 // This will trigger either when the compositor sends the shutdown signal
133 // or when it exits (in which case the stream receives an error)
134 keepalive_stream.next().await;
135
136 Client::signal_state().shutdown();
137}
138
139trait BlockOnTokio {
140 type Output;
141
142 fn block_on_tokio(self) -> Self::Output;
143}
144
145impl<F: Future> BlockOnTokio for F {
146 type Output = F::Output;
147
148 /// Blocks on a future using the current Tokio runtime.
149 fn block_on_tokio(self) -> Self::Output {
150 tokio::task::block_in_place(|| {
151 let handle = tokio::runtime::Handle::current();
152 handle.block_on(self)
153 })
154 }
155}
156
157/// Defines the config's main entry point.
158///
159/// This macro creates a `main` function annotated with
160/// `#[pinnacle_api::tokio::main]` that performs necessary setup
161/// and calls the provided async function.
162///
163/// # Examples
164///
165/// ```no_run
166/// async fn config() {}
167///
168/// pinnacle_api::main!(config);
169/// ```
170#[macro_export]
171macro_rules! main {
172 ($func:ident) => {
173 #[$crate::tokio::main(crate = "pinnacle_api::tokio")]
174 async fn main() {
175 $crate::config!($func);
176 }
177 };
178}
179
180/// Connects to Pinnacle before calling the provided async function,
181/// then blocks until Pinnacle exits.
182///
183/// This macro is called by [`main`]. It is exposed for use in case you
184/// need to change the generated main function.
185///
186/// # Examples
187///
188/// ```no_run
189/// async fn config() {}
190///
191/// #[pinnacle_api::tokio::main(worker_threads = 8)]
192/// async fn main() {
193/// pinnacle_api::config!(config);
194/// }
195/// ```
196#[macro_export]
197macro_rules! config {
198 ($func:ident) => {{
199 let (send, mut recv) = $crate::tokio::sync::mpsc::unbounded_channel();
200
201 let hook = ::std::panic::take_hook();
202 ::std::panic::set_hook(::std::boxed::Box::new(move |info| {
203 hook(info);
204 let backtrace = ::std::backtrace::Backtrace::force_capture();
205 let error_msg = format!("{info}\n{backtrace}");
206 let _ = send.send(error_msg);
207 }));
208
209 $crate::connect().await.unwrap();
210 $func().await;
211 $crate::tokio::select! {
212 Some(error) = recv.recv() => {
213 $crate::pinnacle::set_last_error(error);
214 }
215 _ = $crate::block() => (),
216 }
217 }};
218}