Transition from struct methods to module functions

This commit is contained in:
Ottatop 2023-10-20 17:02:00 -05:00
parent 788910e503
commit 0e32f52972
11 changed files with 515 additions and 546 deletions

View file

@ -1,71 +1,66 @@
use pinnacle_api::prelude::*;
use pinnacle_api::*;
fn main() {
pinnacle_api::setup(|pinnacle| {
let input = pinnacle.input;
let window = pinnacle.window;
let process = pinnacle.process;
let tag = pinnacle.tag;
let output = pinnacle.output;
pinnacle::setup(|| {
let mod_key = Modifier::Ctrl;
let terminal = "alacritty";
// TODO: set env
process::set_env("MOZ_ENABLE_WAYLAND", "1");
input.mousebind(&[mod_key], MouseButton::Left, MouseEdge::Press, move || {
window.begin_move(MouseButton::Left);
input::mousebind(&[mod_key], MouseButton::Left, MouseEdge::Press, move || {
window::begin_move(MouseButton::Left);
});
input.mousebind(
input::mousebind(
&[mod_key],
MouseButton::Right,
MouseEdge::Press,
move || {
window.begin_resize(MouseButton::Right);
window::begin_resize(MouseButton::Right);
},
);
input.keybind(&[mod_key, Modifier::Alt], 'q', move || pinnacle.quit());
input::keybind(&[mod_key, Modifier::Alt], 'q', pinnacle::quit);
input.keybind(&[mod_key, Modifier::Alt], 'c', move || {
if let Some(window) = window.get_focused() {
input::keybind(&[mod_key, Modifier::Alt], 'c', move || {
if let Some(window) = window::get_focused() {
window.close();
}
});
input.keybind(&[mod_key], xkbcommon::xkb::keysyms::KEY_Return, move || {
process.spawn(vec![terminal]).unwrap();
input::keybind(&[mod_key], xkbcommon::xkb::keysyms::KEY_Return, move || {
process::spawn(vec![terminal]).unwrap();
});
input.keybind(
input::keybind(
&[mod_key, Modifier::Alt],
xkbcommon::xkb::keysyms::KEY_space,
move || {
if let Some(window) = window.get_focused() {
if let Some(window) = window::get_focused() {
window.toggle_floating();
}
},
);
input.keybind(&[mod_key], 'f', move || {
if let Some(window) = window.get_focused() {
input::keybind(&[mod_key], 'f', move || {
if let Some(window) = window::get_focused() {
window.toggle_fullscreen();
}
});
input.keybind(&[mod_key], 'm', move || {
if let Some(window) = window.get_focused() {
input::keybind(&[mod_key], 'm', move || {
if let Some(window) = window::get_focused() {
window.toggle_maximized();
}
});
let tags = ["1", "2", "3", "4", "5"];
output.connect_for_all(move |output| {
tag.add(&output, tags.as_slice());
tag.get("1", Some(&output)).unwrap().toggle();
output::connect_for_all(move |output| {
tag::add(&output, tags.as_slice());
tag::get("1", Some(&output)).unwrap().toggle();
});
// let layout_cycler = tag.layout_cycler(&[
@ -84,35 +79,34 @@ fn main() {
for tag_name in tags.iter().map(|t| t.to_string()) {
let t = tag_name.clone();
input.keybind(&[mod_key], tag_name.chars().next().unwrap(), move || {
println!("tag name is {t}");
tag.get(&t, None).unwrap().switch_to();
input::keybind(&[mod_key], tag_name.chars().next().unwrap(), move || {
tag::get(&t, None).unwrap().switch_to();
});
let t = tag_name.clone();
input.keybind(
input::keybind(
&[mod_key, Modifier::Shift],
tag_name.chars().next().unwrap(),
move || {
tag.get(&t, None).unwrap().toggle();
tag::get(&t, None).unwrap().toggle();
},
);
let t = tag_name.clone();
input.keybind(
input::keybind(
&[mod_key, Modifier::Alt],
tag_name.chars().next().unwrap(),
move || {
if let Some(window) = window.get_focused() {
window.move_to_tag(&tag.get(&t, None).unwrap());
if let Some(window) = window::get_focused() {
window.move_to_tag(&tag::get(&t, None).unwrap());
}
},
);
let t = tag_name.clone();
input.keybind(
input::keybind(
&[mod_key, Modifier::Shift, Modifier::Alt],
tag_name.chars().next().unwrap(),
move || {
if let Some(window) = window.get_focused() {
window.toggle_tag(&tag.get(&t, None).unwrap());
if let Some(window) = window::get_focused() {
window.toggle_tag(&tag::get(&t, None).unwrap());
}
},
);

View file

@ -1,3 +1,5 @@
//! Input management.
pub mod libinput;
use xkbcommon::xkb::Keysym;
@ -7,105 +9,86 @@ use crate::{
send_msg, CALLBACK_VEC,
};
use self::libinput::Libinput;
/// Set a keybind.
///
/// This function takes in three parameters:
/// - `modifiers`: A slice of the modifiers you want held for the keybind to trigger.
/// - `key`: The key that needs to be pressed. This takes `impl Into<KeyIntOrString>` and can
/// take the following three types:
/// - [`char`]: A character of the key you want. This can be `a`, `~`, `@`, and so on.
/// - [`u32`]: The key in numeric form. You can use the keys defined in
/// [`xkbcommon::xkb::keysyms`] for this.
/// - [`Keysym`]: The key in `Keysym` form, from [xkbcommon::xkb::Keysym].
pub fn keybind<F>(modifiers: &[Modifier], key: impl Into<KeyIntOrString>, mut action: F)
where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
/// Input management.
#[derive(Clone, Copy)]
pub struct Input {
/// Libinput settings.
///
/// Here you can set stuff like pointer acceleration.
pub libinput: Libinput,
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let key = key.into();
let msg = Msg::SetKeybind {
key,
modifiers: modifiers.to_vec(),
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
impl Input {
/// Set a keybind.
///
/// This function takes in three parameters:
/// - `modifiers`: A slice of the modifiers you want held for the keybind to trigger.
/// - `key`: The key that needs to be pressed. This takes `impl Into<KeyIntOrString>` and can
/// take the following three types:
/// - [`char`]: A character of the key you want. This can be `a`, `~`, `@`, and so on.
/// - [`u32`]: The key in numeric form. You can use the keys defined in
/// [`xkbcommon::xkb::keysyms`] for this.
/// - [`Keysym`]: The key in `Keysym` form, from [xkbcommon::xkb::Keysym].
pub fn keybind<F>(&self, modifiers: &[Modifier], key: impl Into<KeyIntOrString>, mut action: F)
where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
/// Set a mousebind. If called with an already existing mousebind, it gets replaced.
///
/// The mousebind can happen either on button press or release, so you must
/// specify which edge you desire.
pub fn mousebind<F>(modifiers: &[Modifier], button: MouseButton, edge: MouseEdge, mut action: F)
where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let key = key.into();
let msg = Msg::SetMousebind {
modifiers: modifiers.to_vec(),
button: button as u32,
edge,
callback_id: CallbackId(len as u32),
};
let msg = Msg::SetKeybind {
key,
modifiers: modifiers.to_vec(),
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
send_msg(msg).unwrap();
}
/// Set the xkbconfig for your keyboard.
///
/// Parameters set to `None` will be set to their default values.
///
/// Read `xkeyboard-config(7)` for more information.
pub fn set_xkb_config(
rules: Option<&str>,
model: Option<&str>,
layout: Option<&str>,
variant: Option<&str>,
options: Option<&str>,
) {
let msg = Msg::SetXkbConfig {
rules: rules.map(|s| s.to_string()),
variant: variant.map(|s| s.to_string()),
layout: layout.map(|s| s.to_string()),
model: model.map(|s| s.to_string()),
options: options.map(|s| s.to_string()),
};
/// Set a mousebind. If called with an already existing mousebind, it gets replaced.
///
/// The mousebind can happen either on button press or release, so you must
/// specify which edge you desire.
pub fn mousebind<F>(
&self,
modifiers: &[Modifier],
button: MouseButton,
edge: MouseEdge,
mut action: F,
) where
F: FnMut() + Send + 'static,
{
let args_callback = move |_: Option<Args>| {
action();
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::SetMousebind {
modifiers: modifiers.to_vec(),
button: button as u32,
edge,
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
/// Set the xkbconfig for your keyboard.
///
/// Parameters set to `None` will be set to their default values.
///
/// Read `xkeyboard-config(7)` for more information.
pub fn set_xkb_config(
&self,
rules: Option<&str>,
model: Option<&str>,
layout: Option<&str>,
variant: Option<&str>,
options: Option<&str>,
) {
let msg = Msg::SetXkbConfig {
rules: rules.map(|s| s.to_string()),
variant: variant.map(|s| s.to_string()),
layout: layout.map(|s| s.to_string()),
model: model.map(|s| s.to_string()),
options: options.map(|s| s.to_string()),
};
send_msg(msg).unwrap();
}
send_msg(msg).unwrap();
}
/// A mouse button.

View file

@ -1,19 +1,13 @@
//! Libinput settings.
use crate::{msg::Msg, send_msg};
/// Libinput settings.
/// Set a libinput setting.
///
/// Here you can set things like pointer acceleration.
#[derive(Clone, Copy)]
pub struct Libinput;
impl Libinput {
/// Set a libinput setting.
///
/// This takes a [`LibinputSetting`] containing what you want set.
pub fn set(&self, setting: LibinputSetting) {
let msg = Msg::SetLibinputSetting(setting);
send_msg(msg).unwrap();
}
/// This takes a [`LibinputSetting`] containing what you want set.
pub fn set(setting: LibinputSetting) {
let msg = Msg::SetLibinputSetting(setting);
send_msg(msg).unwrap();
}
/// The acceleration profile.

View file

@ -1,20 +1,14 @@
//! The Rust implementation of API for Pinnacle, a Wayland compositor.
#![warn(missing_docs)]
//! The Rust implementation of the Pinnacle API.
mod input;
pub mod input;
mod msg;
mod output;
mod process;
mod tag;
mod window;
use input::libinput::Libinput;
use input::Input;
use output::Output;
use tag::Tag;
use window::rules::WindowRules;
use window::Window;
pub mod output;
pub mod pinnacle;
pub mod process;
pub mod tag;
pub mod window;
/// The xkbcommon crate, re-exported for your convenience.
pub use xkbcommon;
@ -38,30 +32,14 @@ pub mod prelude {
pub use crate::window::FullscreenOrMaximized;
}
/// Re-exports of every config struct.
///
/// Usually you can just use the [`Pinnacle`][crate::Pinnacle] struct passed into
/// the `setup` function, but if you need access to these elsewhere, here they are.
pub mod modules {
pub use crate::input::libinput::Libinput;
pub use crate::input::Input;
pub use crate::output::Output;
pub use crate::process::Process;
pub use crate::tag::Tag;
pub use crate::window::rules::WindowRules;
pub use crate::window::Window;
}
use std::{
collections::HashMap,
io::{Read, Write},
os::unix::net::UnixStream,
path::PathBuf,
sync::{atomic::AtomicU32, Mutex, OnceLock},
};
use msg::{Args, CallbackId, IncomingMsg, Msg, Request, RequestResponse};
use process::Process;
use crate::msg::RequestId;
@ -75,58 +53,6 @@ lazy_static::lazy_static! {
static REQUEST_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
/// Setup Pinnacle.
pub fn setup(config_func: impl FnOnce(Pinnacle)) -> anyhow::Result<()> {
STREAM
.set(Mutex::new(UnixStream::connect(PathBuf::from(
std::env::var("PINNACLE_SOCKET").unwrap_or("/tmp/pinnacle_socket".to_string()),
))?))
.unwrap();
let pinnacle = Pinnacle {
process: Process,
input: Input { libinput: Libinput },
window: Window { rules: WindowRules },
output: Output,
tag: Tag,
};
config_func(pinnacle);
loop {
let mut unread_callback_msgs = UNREAD_CALLBACK_MSGS.lock().unwrap();
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let mut to_remove = vec![];
for (cb_id, incoming_msg) in unread_callback_msgs.iter() {
let IncomingMsg::CallCallback { callback_id, args } = incoming_msg else {
continue;
};
let Some(f) = callback_vec.get_mut(callback_id.0 as usize) else {
continue;
};
f(args.clone());
to_remove.push(*cb_id);
}
for id in to_remove {
unread_callback_msgs.remove(&id);
}
let incoming_msg = read_msg(None);
assert!(matches!(incoming_msg, IncomingMsg::CallCallback { .. }));
let IncomingMsg::CallCallback { callback_id, args } = incoming_msg else {
unreachable!()
};
let Some(f) = callback_vec.get_mut(callback_id.0 as usize) else {
continue;
};
f(args);
}
}
fn send_msg(msg: Msg) -> anyhow::Result<()> {
let mut msg = rmp_serde::encode::to_vec_named(&msg)?;
let mut msg_len = (msg.len() as u32).to_ne_bytes();
@ -208,27 +134,3 @@ fn request(request: Request) -> RequestResponse {
response
}
/// The entry to configuration.
///
/// This struct houses every submodule you'll need to configure Pinnacle.
#[derive(Clone, Copy)]
pub struct Pinnacle {
/// Process management.
pub process: Process,
/// Window management.
pub window: Window,
/// Input management.
pub input: Input,
/// Output management.
pub output: Output,
/// Tag management.
pub tag: Tag,
}
impl Pinnacle {
/// Quit Pinnacle.
pub fn quit(&self) {
send_msg(Msg::Quit).unwrap();
}
}

View file

@ -8,10 +8,10 @@ use crate::{
};
#[derive(Debug, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct CallbackId(pub u32);
pub(crate) struct CallbackId(pub u32);
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WindowRuleCondition {
pub(crate) struct WindowRuleCondition {
/// This condition is met when any of the conditions provided is met.
#[serde(default)]
pub cond_any: Option<Vec<WindowRuleCondition>>,
@ -30,7 +30,7 @@ pub struct WindowRuleCondition {
}
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct WindowRule {
pub(crate) struct WindowRule {
/// Set the output the window will open on.
#[serde(default)]
pub output: Option<OutputName>,
@ -184,7 +184,7 @@ pub(crate) enum Msg {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
/// Messages that require a server response, usually to provide some data.
pub enum Request {
pub(crate) enum Request {
// Windows
GetWindows,
GetWindowProps { window_id: WindowId },
@ -221,7 +221,7 @@ pub enum Args {
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum IncomingMsg {
pub(crate) enum IncomingMsg {
CallCallback {
callback_id: CallbackId,
#[serde(default)]
@ -234,7 +234,7 @@ pub enum IncomingMsg {
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestResponse {
pub(crate) enum RequestResponse {
Window {
window_id: Option<WindowId>,
},

View file

@ -1,7 +1,9 @@
//! Output management.
use crate::{
msg::{Args, CallbackId, Msg, Request, RequestResponse},
request, send_msg,
tag::{Tag, TagHandle},
tag::TagHandle,
CALLBACK_VEC,
};
@ -9,86 +11,80 @@ use crate::{
///
/// An empty string represents an invalid output.
#[derive(Debug, Hash, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct OutputName(pub String);
pub(crate) struct OutputName(pub String);
/// Output management.
#[derive(Clone, Copy)]
pub struct Output;
/// Get an [`OutputHandle`] by its name.
///
/// `name` is the name of the port the output is plugged in to.
/// This is something like `HDMI-1` or `eDP-0`.
pub fn get_by_name(name: &str) -> Option<OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
impl Output {
/// Get an [`OutputHandle`] by its name.
///
/// `name` is the name of the port the output is plugged in to.
/// This is something like `HDMI-1` or `eDP-0`.
pub fn get_by_name(&self, name: &str) -> Option<OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
output_names
.into_iter()
.find(|s| s == name)
.map(|s| OutputHandle(OutputName(s)))
}
output_names
.into_iter()
.find(|s| s == name)
.map(|s| OutputHandle(OutputName(s)))
}
/// Get a handle to all connected outputs.
pub fn get_all() -> impl Iterator<Item = OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
/// Get a handle to all connected outputs.
pub fn get_all(&self) -> impl Iterator<Item = OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
output_names
.into_iter()
.map(|name| OutputHandle(OutputName(name)))
}
output_names
.into_iter()
.map(|name| OutputHandle(OutputName(name)))
}
/// Get the currently focused output.
///
/// This is currently defined as the one with the cursor on it.
pub fn get_focused() -> Option<OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
/// Get the currently focused output.
///
/// This is currently defined as the one with the cursor on it.
pub fn get_focused(&self) -> Option<OutputHandle> {
let RequestResponse::Outputs { output_names } = request(Request::GetOutputs) else {
unreachable!()
};
output_names
.into_iter()
.map(|s| OutputHandle(OutputName(s)))
.find(|op| op.properties().focused == Some(true))
}
output_names
.into_iter()
.map(|s| OutputHandle(OutputName(s)))
.find(|op| op.properties().focused == Some(true))
}
/// Connect a function to be run on all current and future outputs.
///
/// When called, `connect_for_all` will run `func` with all currently connected outputs.
/// If a new output is connected, `func` will also be called with it.
///
/// This will *not* be called if it has already been called for a given connector.
/// This means turning your monitor off and on or unplugging and replugging it *to the same port*
/// won't trigger `func`. Plugging it in to a new port *will* trigger `func`.
/// This is intended to prevent duplicate setup.
///
/// Please note: this function will be run *after* Pinnacle processes your entire config.
/// For example, if you define tags in `func` but toggle them directly after `connect_for_all`,
/// nothing will happen as the tags haven't been added yet.
pub fn connect_for_all<F>(mut func: F)
where
F: FnMut(OutputHandle) + Send + 'static,
{
let args_callback = move |args: Option<Args>| {
if let Some(Args::ConnectForAllOutputs { output_name }) = args {
func(OutputHandle(OutputName(output_name)));
}
};
/// Connect a function to be run on all current and future outputs.
///
/// When called, `connect_for_all` will run `func` with all currently connected outputs.
/// If a new output is connected, `func` will also be called with it.
///
/// This will *not* be called if it has already been called for a given connector.
/// This means turning your monitor off and on or unplugging and replugging it *to the same port*
/// won't trigger `func`. Plugging it in to a new port *will* trigger `func`.
/// This is intended to prevent duplicate setup.
///
/// Please note: this function will be run *after* Pinnacle processes your entire config.
/// For example, if you define tags in `func` but toggle them directly after `connect_for_all`,
/// nothing will happen as the tags haven't been added yet.
pub fn connect_for_all<F>(&self, mut func: F)
where
F: FnMut(OutputHandle) + Send + 'static,
{
let args_callback = move |args: Option<Args>| {
if let Some(Args::ConnectForAllOutputs { output_name }) = args {
func(OutputHandle(OutputName(output_name)));
}
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::ConnectForAllOutputs {
callback_id: CallbackId(len as u32),
};
let msg = Msg::ConnectForAllOutputs {
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
send_msg(msg).unwrap();
}
/// An output handle.
@ -97,7 +93,7 @@ impl Output {
/// It serves to make it easier to deal with them, defining methods for getting properties and
/// helpers for things like positioning multiple monitors.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct OutputHandle(pub OutputName);
pub struct OutputHandle(pub(crate) OutputName);
/// Properties of an output.
pub struct OutputProperties {
@ -126,6 +122,11 @@ pub struct OutputProperties {
}
impl OutputHandle {
/// Get this output's name.
pub fn name(&self) -> String {
self.0 .0.clone()
}
// TODO: Make OutputProperties an option, make non null fields not options
/// Get all properties of this output.
pub fn properties(&self) -> OutputProperties {
@ -161,10 +162,12 @@ impl OutputHandle {
}
}
/// Add tags with the given `names` to this output.
pub fn add_tags(&self, names: &[&str]) {
Tag.add(self, names);
crate::tag::add(self, names);
}
/// Set this output's location in the global space.
pub fn set_loc(&self, x: Option<i32>, y: Option<i32>) {
let msg = Msg::SetOutputLocation {
output_name: self.0.clone(),
@ -175,18 +178,30 @@ impl OutputHandle {
send_msg(msg).unwrap();
}
/// Set this output's location to the right of `other`.
///
/// It will be aligned vertically based on the given `alignment`.
pub fn set_loc_right_of(&self, other: &OutputHandle, alignment: AlignmentVertical) {
self.set_loc_horizontal(other, LeftOrRight::Right, alignment);
}
/// Set this output's location to the left of `other`.
///
/// It will be aligned vertically based on the given `alignment`.
pub fn set_loc_left_of(&self, other: &OutputHandle, alignment: AlignmentVertical) {
self.set_loc_horizontal(other, LeftOrRight::Left, alignment);
}
/// Set this output's location to the top of `other`.
///
/// It will be aligned horizontally based on the given `alignment`.
pub fn set_loc_top_of(&self, other: &OutputHandle, alignment: AlignmentHorizontal) {
self.set_loc_vertical(other, TopOrBottom::Top, alignment);
}
/// Set this output's location to the bottom of `other`.
///
/// It will be aligned horizontally based on the given `alignment`.
pub fn set_loc_bottom_of(&self, other: &OutputHandle, alignment: AlignmentHorizontal) {
self.set_loc_vertical(other, TopOrBottom::Bottom, alignment);
}

66
api/rust/src/pinnacle.rs Normal file
View file

@ -0,0 +1,66 @@
//! Functions for compositor control, like `setup` and `quit`.
use std::{
collections::hash_map::Entry, convert::Infallible, os::unix::net::UnixStream, path::PathBuf,
sync::Mutex,
};
use crate::{
msg::{IncomingMsg, Msg},
read_msg, send_msg, CALLBACK_VEC, STREAM, UNREAD_CALLBACK_MSGS,
};
/// Quit Pinnacle.
pub fn quit() {
send_msg(Msg::Quit).unwrap();
}
/// Setup Pinnacle.
///
/// This will attempt to connect to the socket at `$PINNACLE_SOCKET`, which should be set by the
/// compositor when opened.
///
/// It will then run your `config_func`.
///
/// Lastly, it will enter a loop to listen to messages coming from Pinnacle.
///
/// If this function returns, an error has occurred.
pub fn setup(config_func: impl FnOnce()) -> anyhow::Result<Infallible> {
STREAM
.set(Mutex::new(UnixStream::connect(PathBuf::from(
std::env::var("PINNACLE_SOCKET").unwrap_or("/tmp/pinnacle_socket".to_string()),
))?))
.unwrap();
config_func();
loop {
let mut unread_callback_msgs = UNREAD_CALLBACK_MSGS.lock().unwrap();
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
for cb_id in unread_callback_msgs.keys().copied().collect::<Vec<_>>() {
let Entry::Occupied(entry) = unread_callback_msgs.entry(cb_id) else {
unreachable!();
};
let IncomingMsg::CallCallback { callback_id, args } = entry.remove() else {
unreachable!();
};
let Some(callback) = callback_vec.get_mut(callback_id.0 as usize) else {
unreachable!();
};
callback(args);
}
let incoming_msg = read_msg(None);
let IncomingMsg::CallCallback { callback_id, args } = incoming_msg else {
unreachable!();
};
let Some(callback) = callback_vec.get_mut(callback_id.0 as usize) else {
unreachable!();
};
callback(args);
}
}

View file

@ -1,73 +1,69 @@
//! Process management.
use crate::{
msg::{Args, CallbackId, Msg},
send_msg, CALLBACK_VEC,
};
/// Process management.
#[derive(Clone, Copy)]
pub struct Process;
/// Spawn a process.
///
/// This will use Rust's (more specifically `async_process`'s) `Command` to spawn the provided
/// arguments. If you are using any shell syntax like `~`, you may need to spawn a shell
/// instead. If so, you may *also* need to correctly escape the input.
pub fn spawn(command: Vec<&str>) -> anyhow::Result<()> {
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: None,
};
impl Process {
/// Spawn a process.
///
/// This will use Rust's (more specifically `async_process`'s) `Command` to spawn the provided
/// arguments. If you are using any shell syntax like `~`, you may need to spawn a shell
/// instead. If so, you may *also* need to correctly escape the input.
pub fn spawn(&self, command: Vec<&str>) -> anyhow::Result<()> {
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: None,
};
send_msg(msg)
}
/// Spawn a process with an optional callback for its stdout, stderr, and exit information.
///
/// `callback` has the following parameters:
/// - `0`: The process's stdout printed this line.
/// - `1`: The process's stderr printed this line.
/// - `2`: The process exited with this code.
/// - `3`: The process exited with this message.
pub fn spawn_with_callback<F>(&self, command: Vec<&str>, mut callback: F) -> anyhow::Result<()>
where
F: FnMut(Option<String>, Option<String>, Option<i32>, Option<String>) + Send + 'static,
{
let args_callback = move |args: Option<Args>| {
if let Some(Args::Spawn {
stdout,
stderr,
exit_code,
exit_msg,
}) = args
{
callback(stdout, stderr, exit_code, exit_msg);
}
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: Some(CallbackId(len as u32)),
};
send_msg(msg)
}
/// Set an environment variable for Pinnacle. All future processes spawned will have this env set.
///
/// Note that this will only set the variable for the compositor, not the running config process.
/// If you need to set an environment variable for this config, place them in the `metaconfig.toml` file instead
/// or use [`std::env::set_var`].
pub fn set_env(&self, key: &str, value: &str) {
let msg = Msg::SetEnv {
key: key.to_string(),
value: value.to_string(),
};
send_msg(msg).unwrap();
}
send_msg(msg)
}
/// Spawn a process with an optional callback for its stdout, stderr, and exit information.
///
/// `callback` has the following parameters:
/// - `0`: The process's stdout printed this line.
/// - `1`: The process's stderr printed this line.
/// - `2`: The process exited with this code.
/// - `3`: The process exited with this message.
pub fn spawn_with_callback<F>(command: Vec<&str>, mut callback: F) -> anyhow::Result<()>
where
F: FnMut(Option<String>, Option<String>, Option<i32>, Option<String>) + Send + 'static,
{
let args_callback = move |args: Option<Args>| {
if let Some(Args::Spawn {
stdout,
stderr,
exit_code,
exit_msg,
}) = args
{
callback(stdout, stderr, exit_code, exit_msg);
}
};
let mut callback_vec = CALLBACK_VEC.lock().unwrap();
let len = callback_vec.len();
callback_vec.push(Box::new(args_callback));
let msg = Msg::Spawn {
command: command.into_iter().map(|s| s.to_string()).collect(),
callback_id: Some(CallbackId(len as u32)),
};
send_msg(msg)
}
/// Set an environment variable for Pinnacle. All future processes spawned will have this env set.
///
/// Note that this will only set the variable for the compositor, not the running config process.
/// If you need to set an environment variable for this config, place them in the `metaconfig.toml` file instead
/// or use [`std::env::set_var`].
pub fn set_env(key: &str, value: &str) {
let msg = Msg::SetEnv {
key: key.to_string(),
value: value.to_string(),
};
send_msg(msg).unwrap();
}

View file

@ -1,106 +1,99 @@
//! Tag management.
use std::collections::HashMap;
use crate::{
msg::{Msg, Request, RequestResponse},
output::{Output, OutputHandle, OutputName},
output::{OutputHandle, OutputName},
request, send_msg,
};
/// Tag management.
#[derive(Clone, Copy)]
pub struct Tag;
impl Tag {
/// Get a tag by its name and output. If `output` is `None`, the currently focused output will
/// be used instead.
///
/// If multiple tags have the same name, this returns the first one.
pub fn get(&self, name: &str, output: Option<&OutputHandle>) -> Option<TagHandle> {
self.get_all()
.filter(|tag| {
tag.properties().output.is_some_and(|op| match output {
Some(output) => &op == output,
None => Some(op) == Output.get_focused(),
})
/// Get a tag by its name and output. If `output` is `None`, the currently focused output will
/// be used instead.
///
/// If multiple tags have the same name, this returns the first one.
pub fn get(name: &str, output: Option<&OutputHandle>) -> Option<TagHandle> {
get_all()
.filter(|tag| {
tag.properties().output.is_some_and(|op| match output {
Some(output) => &op == output,
None => Some(op) == crate::output::get_focused(),
})
.find(|tag| tag.properties().name.is_some_and(|s| s == name))
}
/// Get all tags.
pub fn get_all(&self) -> impl Iterator<Item = TagHandle> {
let RequestResponse::Tags { tag_ids } = request(Request::GetTags) else {
unreachable!()
};
tag_ids.into_iter().map(|t| {
println!("got tag id {t:?}");
TagHandle(t)
})
}
.find(|tag| tag.properties().name.is_some_and(|s| s == name))
}
// TODO: return taghandles here
/// Add tags with the names from `names` to `output`.
pub fn add(&self, output: &OutputHandle, names: &[&str]) {
let msg = Msg::AddTags {
output_name: output.0.clone(),
tag_names: names.iter().map(|s| s.to_string()).collect(),
/// Get all tags.
pub fn get_all() -> impl Iterator<Item = TagHandle> {
let RequestResponse::Tags { tag_ids } = request(Request::GetTags) else {
unreachable!()
};
tag_ids.into_iter().map(TagHandle)
}
// TODO: return taghandles here
/// Add tags with the names from `names` to `output`.
pub fn add(output: &OutputHandle, names: &[&str]) {
let msg = Msg::AddTags {
output_name: output.0.clone(),
tag_names: names.iter().map(|s| s.to_string()).collect(),
};
send_msg(msg).unwrap();
}
/// Create a `LayoutCycler` to cycle layouts on tags.
///
/// Given a slice of layouts, this will create a `LayoutCycler` with two methods;
/// one will cycle forward the layout for the active tag, and one will cycle backward.
///
/// # Example
/// ```
/// todo!()
/// ```
pub fn layout_cycler(layouts: &[Layout]) -> LayoutCycler {
let mut indices = HashMap::<TagId, usize>::new();
let layouts = layouts.to_vec();
let len = layouts.len();
let cycle = move |cycle: Cycle, output: Option<&OutputHandle>| {
let Some(output) = output.cloned().or_else(crate::output::get_focused) else {
return;
};
send_msg(msg).unwrap();
}
let Some(tag) = output
.properties()
.tags
.into_iter()
.find(|tag| tag.properties().active == Some(true))
else {
return;
};
/// Create a `LayoutCycler` to cycle layouts on tags.
///
/// Given a slice of layouts, this will create a `LayoutCycler` with two methods;
/// one will cycle forward the layout for the active tag, and one will cycle backward.
///
/// # Example
/// ```
/// todo!()
/// ```
pub fn layout_cycler(&self, layouts: &[Layout]) -> LayoutCycler {
let mut indices = HashMap::<TagId, usize>::new();
let layouts = layouts.to_vec();
let len = layouts.len();
let cycle = move |cycle: Cycle, output: Option<&OutputHandle>| {
let Some(output) = output.cloned().or_else(|| Output.get_focused()) else {
return;
};
let index = indices.entry(tag.0).or_insert(0);
let Some(tag) = output
.properties()
.tags
.into_iter()
.find(|tag| tag.properties().active == Some(true))
else {
return;
};
let index = indices.entry(tag.0).or_insert(0);
match cycle {
Cycle::Forward => {
if *index + 1 >= len {
*index = 0;
} else {
*index += 1;
}
}
Cycle::Backward => {
if index.wrapping_sub(1) == usize::MAX {
*index = len - 1;
} else {
*index -= 1;
}
match cycle {
Cycle::Forward => {
if *index + 1 >= len {
*index = 0;
} else {
*index += 1;
}
}
Cycle::Backward => {
if index.wrapping_sub(1) == usize::MAX {
*index = len - 1;
} else {
*index -= 1;
}
}
tag.set_layout(layouts[*index]);
};
LayoutCycler {
cycle: Box::new(cycle),
}
tag.set_layout(layouts[*index]);
};
LayoutCycler {
cycle: Box::new(cycle),
}
}
@ -121,32 +114,42 @@ pub struct LayoutCycler {
}
impl LayoutCycler {
/// Cycle to the next layout for the first active tag on `output`.
/// If `output` is `None`, the currently focused output is used.
pub fn next(&mut self, output: Option<&OutputHandle>) {
(self.cycle)(Cycle::Forward, output);
}
/// Cycle to the previous layout for the first active tag on `output`.
/// If `output` is `None`, the currently focused output is used.
pub fn prev(&mut self, output: Option<&OutputHandle>) {
(self.cycle)(Cycle::Backward, output);
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum TagId {
pub(crate) enum TagId {
None,
#[serde(untagged)]
Some(u32),
}
pub struct TagHandle(pub TagId);
/// A handle to a tag.
pub struct TagHandle(pub(crate) TagId);
/// Properties of a tag, retrieved through [`TagHandle::properties`].
#[derive(Debug)]
pub struct TagProperties {
/// Whether or not the tag is active.
active: Option<bool>,
/// The tag's name.
name: Option<String>,
/// The output the tag is on.
output: Option<OutputHandle>,
}
impl TagHandle {
/// Get this tag's [`TagProperties`].
pub fn properties(&self) -> TagProperties {
let RequestResponse::TagProps {
active,
@ -164,16 +167,19 @@ impl TagHandle {
}
}
/// Toggle this tag.
pub fn toggle(&self) {
let msg = Msg::ToggleTag { tag_id: self.0 };
send_msg(msg).unwrap();
}
/// Switch to this tag, deactivating all others on its output.
pub fn switch_to(&self) {
let msg = Msg::SwitchToTag { tag_id: self.0 };
send_msg(msg).unwrap();
}
/// Set this tag's [`Layout`].
pub fn set_layout(&self, layout: Layout) {
let msg = Msg::SetLayout {
tag_id: self.0,

View file

@ -1,3 +1,5 @@
//! Window management.
pub mod rules;
use crate::{
@ -7,11 +9,9 @@ use crate::{
tag::TagHandle,
};
use self::rules::WindowRules;
/// A unique identifier for each window.
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WindowId {
pub(crate) enum WindowId {
/// A config API returned an invalid window. It should be using this variant.
None,
/// A valid window id.
@ -19,86 +19,97 @@ pub enum WindowId {
Some(u32),
}
/// Window management.
#[derive(Clone, Copy)]
pub struct Window {
/// Window rules.
pub rules: WindowRules,
/// Get all windows with the class `class`.
pub fn get_by_class(class: &str) -> impl Iterator<Item = WindowHandle> + '_ {
get_all().filter(|win| win.properties().class.as_deref() == Some(class))
}
impl Window {
/// Get all windows with the class `class`.
pub fn get_by_class<'a>(&self, class: &'a str) -> impl Iterator<Item = WindowHandle> + 'a {
self.get_all()
.filter(|win| win.properties().class.as_deref() == Some(class))
}
/// Get the currently focused window, or `None` if there isn't one.
pub fn get_focused(&self) -> Option<WindowHandle> {
self.get_all()
.find(|win| win.properties().focused.is_some_and(|focused| focused))
}
/// Get all windows.
pub fn get_all(&self) -> impl Iterator<Item = WindowHandle> {
let RequestResponse::Windows { window_ids } = request(Request::GetWindows) else {
unreachable!()
};
window_ids.into_iter().map(WindowHandle)
}
/// Begin a window move.
///
/// This will start a window move grab with the provided button on the window the pointer
/// is currently hovering over. Once `button` is let go, the move will end.
pub fn begin_move(&self, button: MouseButton) {
let msg = Msg::WindowMoveGrab {
button: button as u32,
};
send_msg(msg).unwrap();
}
/// Begin a window resize.
///
/// This will start a window resize grab with the provided button on the window the
/// pointer is currently hovering over. Once `button` is let go, the resize will end.
pub fn begin_resize(&self, button: MouseButton) {
let msg = Msg::WindowResizeGrab {
button: button as u32,
};
send_msg(msg).unwrap();
}
/// Get the currently focused window, or `None` if there isn't one.
pub fn get_focused() -> Option<WindowHandle> {
get_all().find(|win| win.properties().focused.is_some_and(|focused| focused))
}
/// Get all windows.
pub fn get_all() -> impl Iterator<Item = WindowHandle> {
let RequestResponse::Windows { window_ids } = request(Request::GetWindows) else {
unreachable!()
};
window_ids.into_iter().map(WindowHandle)
}
/// Begin a window move.
///
/// This will start a window move grab with the provided button on the window the pointer
/// is currently hovering over. Once `button` is let go, the move will end.
pub fn begin_move(button: MouseButton) {
let msg = Msg::WindowMoveGrab {
button: button as u32,
};
send_msg(msg).unwrap();
}
/// Begin a window resize.
///
/// This will start a window resize grab with the provided button on the window the
/// pointer is currently hovering over. Once `button` is let go, the resize will end.
pub fn begin_resize(button: MouseButton) {
let msg = Msg::WindowResizeGrab {
button: button as u32,
};
send_msg(msg).unwrap();
}
/// A handle to a window.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WindowHandle(WindowId);
/// Properties of a window, retrieved through [`WindowHandle::properties`].
#[derive(Debug)]
pub struct WindowProperties {
/// The size of the window, in pixels.
pub size: Option<(i32, i32)>,
/// The location of the window in the global space.
pub loc: Option<(i32, i32)>,
/// The window's class.
pub class: Option<String>,
/// The window's title.
pub title: Option<String>,
/// Whether or not the window is focused.
pub focused: Option<bool>,
/// Whether or not the window is floating.
pub floating: Option<bool>,
/// Whether the window is fullscreen, maximized, or neither.
pub fullscreen_or_maximized: Option<FullscreenOrMaximized>,
}
impl WindowHandle {
/// Toggle this window between floating and tiled.
pub fn toggle_floating(&self) {
send_msg(Msg::ToggleFloating { window_id: self.0 }).unwrap();
}
/// Toggle this window's fullscreen status.
///
/// If used while not fullscreen, it becomes fullscreen.
/// If used while fullscreen, it becomes unfullscreen.
/// If used while maximized, it becomes fullscreen.
pub fn toggle_fullscreen(&self) {
send_msg(Msg::ToggleFullscreen { window_id: self.0 }).unwrap();
}
/// Toggle this window's maximized status.
///
/// If used while not maximized, it becomes maximized.
/// If used while maximized, it becomes unmaximized.
/// If used while fullscreen, it becomes maximized.
pub fn toggle_maximized(&self) {
send_msg(Msg::ToggleMaximized { window_id: self.0 }).unwrap();
}
/// Set this window's size. None parameters will be ignored.
pub fn set_size(&self, width: Option<i32>, height: Option<i32>) {
send_msg(Msg::SetWindowSize {
window_id: self.0,
@ -108,10 +119,12 @@ impl WindowHandle {
.unwrap();
}
/// Send a close event to this window.
pub fn close(&self) {
send_msg(Msg::CloseWindow { window_id: self.0 }).unwrap();
}
/// Get this window's [`WindowProperties`].
pub fn properties(&self) -> WindowProperties {
let RequestResponse::WindowProps {
size,
@ -137,6 +150,7 @@ impl WindowHandle {
}
}
/// Toggle `tag` on this window.
pub fn toggle_tag(&self, tag: &TagHandle) {
let msg = Msg::ToggleTagOnWindow {
window_id: self.0,
@ -146,6 +160,9 @@ impl WindowHandle {
send_msg(msg).unwrap();
}
/// Move this window to `tag`.
///
/// This will remove all other tags on this window.
pub fn move_to_tag(&self, tag: &TagHandle) {
let msg = Msg::MoveWindowToTag {
window_id: self.0,

View file

@ -1,23 +1,19 @@
//! Window rules.
use std::num::NonZeroU32;
use crate::{msg::Msg, output::OutputHandle, send_msg, tag::TagHandle};
use super::{FloatingOrTiled, FullscreenOrMaximized};
/// Window rules.
#[derive(Clone, Copy)]
pub struct WindowRules;
/// Add a window rule.
pub fn add(cond: WindowRuleCondition, rule: WindowRule) {
let msg = Msg::AddWindowRule {
cond: cond.0,
rule: rule.0,
};
impl WindowRules {
/// Add a window rule.
pub fn add(&self, cond: WindowRuleCondition, rule: WindowRule) {
let msg = Msg::AddWindowRule {
cond: cond.0,
rule: rule.0,
};
send_msg(msg).unwrap();
}
send_msg(msg).unwrap();
}
/// A window rule.