diff --git a/api/rust/examples/example_config.rs b/api/rust/examples/example_config.rs index bb312c1..53ec9cb 100644 --- a/api/rust/examples/example_config.rs +++ b/api/rust/examples/example_config.rs @@ -2,9 +2,6 @@ use pinnacle_api::prelude::*; use pinnacle_api::*; fn main() { - // let mut num = 5; - // let test = &mut num; - pinnacle_api::connect().unwrap(); let mod_key = Modifier::Ctrl; @@ -13,65 +10,97 @@ fn main() { process::set_env("MOZ_ENABLE_WAYLAND", "1"); - input::mousebind(&[mod_key], MouseButton::Left, MouseEdge::Press, move || { - window::begin_move(MouseButton::Left); - }); + let mut callback_vec = CallbackVec::new(); + + input::mousebind( + &[mod_key], + MouseButton::Left, + MouseEdge::Press, + move |_| { + window::begin_move(MouseButton::Left); + }, + &mut callback_vec, + ); input::mousebind( &[mod_key], MouseButton::Right, MouseEdge::Press, - move || { + move |_| { window::begin_resize(MouseButton::Right); }, + &mut callback_vec, ); - input::keybind(&[mod_key, Modifier::Alt], 'q', pinnacle::quit); + input::keybind( + &[mod_key, Modifier::Alt], + 'q', + |_| pinnacle::quit(), + &mut callback_vec, + ); - input::keybind(&[mod_key, Modifier::Alt], 'c', move || { - if let Some(window) = window::get_focused() { - window.close(); - } - }); + input::keybind( + &[mod_key, Modifier::Alt], + 'c', + move |_| { + if let Some(window) = window::get_focused() { + window.close(); + } + }, + &mut callback_vec, + ); - 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(); + }, + &mut callback_vec, + ); input::keybind( &[mod_key, Modifier::Alt], xkbcommon::xkb::keysyms::KEY_space, - move || { + move |_| { if let Some(window) = window::get_focused() { window.toggle_floating(); } }, + &mut callback_vec, ); - input::keybind(&[mod_key], 'f', move || { - if let Some(window) = window::get_focused() { - window.toggle_fullscreen(); - } - }); + input::keybind( + &[mod_key], + 'f', + move |_| { + if let Some(window) = window::get_focused() { + window.toggle_fullscreen(); + } + }, + &mut callback_vec, + ); - input::keybind(&[mod_key], 'm', move || { - if let Some(window) = window::get_focused() { - window.toggle_maximized(); - } - }); + input::keybind( + &[mod_key], + 'm', + move |_| { + if let Some(window) = window::get_focused() { + window.toggle_maximized(); + } + }, + &mut callback_vec, + ); 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(); - }); - - // let mut num = 5; - // - // input::keybind(&[mod_key], 'm', || { - // num += 1; - // }); + output::connect_for_all( + move |output, _| { + tag::add(&output, tags.as_slice()); + tag::get("1", Some(&output)).unwrap().toggle(); + }, + &mut callback_vec, + ); // let layout_cycler = tag.layout_cycler(&[ // Layout::MasterStack, @@ -89,38 +118,46 @@ 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 || { - tag::get(&t, None).unwrap().switch_to(); - }); + input::keybind( + &[mod_key], + tag_name.chars().next().unwrap(), + move |_| { + tag::get(&t, None).unwrap().switch_to(); + }, + &mut callback_vec, + ); let t = tag_name.clone(); input::keybind( &[mod_key, Modifier::Shift], tag_name.chars().next().unwrap(), - move || { + move |_| { tag::get(&t, None).unwrap().toggle(); }, + &mut callback_vec, ); let t = tag_name.clone(); input::keybind( &[mod_key, Modifier::Alt], tag_name.chars().next().unwrap(), - move || { + move |_| { if let Some(window) = window::get_focused() { window.move_to_tag(&tag::get(&t, None).unwrap()); } }, + &mut callback_vec, ); let t = tag_name.clone(); input::keybind( &[mod_key, Modifier::Shift, Modifier::Alt], tag_name.chars().next().unwrap(), - move || { + move |_| { if let Some(window) = window::get_focused() { window.toggle_tag(&tag::get(&t, None).unwrap()); } }, + &mut callback_vec, ); } - pinnacle_api::listen(); + pinnacle_api::listen(callback_vec); } diff --git a/api/rust/src/input.rs b/api/rust/src/input.rs index 0dbe95b..bf636b2 100644 --- a/api/rust/src/input.rs +++ b/api/rust/src/input.rs @@ -6,7 +6,7 @@ use xkbcommon::xkb::Keysym; use crate::{ msg::{Args, CallbackId, KeyIntOrString, Msg}, - send_msg, CALLBACK_VEC, + send_msg, CallbackVec, }; /// Set a keybind. @@ -18,17 +18,22 @@ use crate::{ /// - [`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(modifiers: &[Modifier], key: impl Into, mut action: F) -where - F: FnMut() + Send + 'static, +/// +/// `action` takes in a `&mut `[`CallbackVec`] for use in the closure. +pub fn keybind<'a, F>( + modifiers: &[Modifier], + key: impl Into, + mut action: F, + callback_vec: &mut CallbackVec<'a>, +) where + F: FnMut(&mut CallbackVec) + 'a, { - let args_callback = move |_: Option| { - action(); + let args_callback = move |_: Option, callback_vec: &mut CallbackVec<'_>| { + action(callback_vec); }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let len = callback_vec.len(); - callback_vec.push(Box::new(args_callback)); + let len = callback_vec.callbacks.len(); + callback_vec.callbacks.push(Box::new(args_callback)); let key = key.into(); @@ -45,17 +50,23 @@ where /// /// The mousebind can happen either on button press or release, so you must /// specify which edge you desire. -pub fn mousebind(modifiers: &[Modifier], button: MouseButton, edge: MouseEdge, mut action: F) -where - F: FnMut() + Send + 'static, +/// +/// `action` takes in a `&mut `[`CallbackVec`] for use in the closure. +pub fn mousebind<'a, F>( + modifiers: &[Modifier], + button: MouseButton, + edge: MouseEdge, + mut action: F, + callback_vec: &mut CallbackVec<'a>, +) where + F: FnMut(&mut CallbackVec) + 'a, { - let args_callback = move |_: Option| { - action(); + let args_callback = move |_: Option, callback_vec: &mut CallbackVec<'_>| { + action(callback_vec); }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let len = callback_vec.len(); - callback_vec.push(Box::new(args_callback)); + let len = callback_vec.callbacks.len(); + callback_vec.callbacks.push(Box::new(args_callback)); let msg = Msg::SetMousebind { modifiers: modifiers.to_vec(), diff --git a/api/rust/src/lib.rs b/api/rust/src/lib.rs index 83ce972..da92307 100644 --- a/api/rust/src/lib.rs +++ b/api/rust/src/lib.rs @@ -46,8 +46,6 @@ use msg::{Args, CallbackId, IncomingMsg, Msg, Request, RequestResponse}; use crate::msg::RequestId; static STREAM: OnceLock> = OnceLock::new(); -#[allow(clippy::type_complexity)] -static CALLBACK_VEC: Mutex) + Send>>> = Mutex::new(Vec::new()); lazy_static::lazy_static! { static ref UNREAD_CALLBACK_MSGS: Mutex> = Mutex::new(HashMap::new()); static ref UNREAD_REQUEST_MSGS: Mutex> = Mutex::new(HashMap::new()); @@ -157,7 +155,7 @@ pub fn connect() -> anyhow::Result<()> { /// Begin listening for messages coming from Pinnacle. /// /// This needs to be called at the very end of your `setup` function. -pub fn listen() -> Infallible { +pub fn listen(mut callback_vec: CallbackVec) -> Infallible { loop { let mut unread_callback_msgs = UNREAD_CALLBACK_MSGS.lock().unwrap(); @@ -168,11 +166,18 @@ pub fn listen() -> Infallible { let IncomingMsg::CallCallback { callback_id, args } = entry.remove() else { unreachable!(); }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let Some(callback) = callback_vec.get_mut(callback_id.0 as usize) else { - unreachable!(); - }; - callback(args); + + // Take the callback out and replace it with a dummy callback + // to allow callback_vec to be used mutably below. + let mut callback = std::mem::replace( + &mut callback_vec.callbacks[callback_id.0 as usize], + Box::new(|_, _| {}), + ); + + callback(args, &mut callback_vec); + + // Put it back. + callback_vec.callbacks[callback_id.0 as usize] = callback; } let incoming_msg = read_msg(None); @@ -181,11 +186,43 @@ pub fn listen() -> Infallible { unreachable!(); }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let Some(callback) = callback_vec.get_mut(callback_id.0 as usize) else { - unreachable!(); - }; + let mut callback = std::mem::replace( + &mut callback_vec.callbacks[callback_id.0 as usize], + Box::new(|_, _| {}), + ); - callback(args); + callback(args, &mut callback_vec); + + callback_vec.callbacks[callback_id.0 as usize] = callback; + } +} + +/// A wrapper around a vector that holds all of your callbacks. +/// +/// You will need to create this before you can start calling config functions +/// that require callbacks. +/// +/// Because your callbacks can capture things, we need a non-static way to hold them. +/// That's where this struct comes in. +/// +/// Every function that needs you to provide a callback will also need you to +/// provide a `&mut CallbackVec`. This will insert the callback for use in [`listen`]. +/// +/// Additionally, all callbacks will also take in `&mut CallbackVec`. This is so you can +/// call functions that need it inside of other callbacks. +/// +/// At the end of your config, you will need to call [`listen`] to begin listening for +/// messages from Pinnacle that will call your callbacks. Here, you must in pass your +/// `CallbackVec`. +#[derive(Default)] +pub struct CallbackVec<'a> { + #[allow(clippy::type_complexity)] + pub(crate) callbacks: Vec, &mut CallbackVec) + 'a>>, +} + +impl<'a> CallbackVec<'a> { + /// Create a new, empty `CallbackVec`. + pub fn new() -> Self { + Default::default() } } diff --git a/api/rust/src/output.rs b/api/rust/src/output.rs index 4d1b07a..e94dc20 100644 --- a/api/rust/src/output.rs +++ b/api/rust/src/output.rs @@ -4,7 +4,7 @@ use crate::{ msg::{Args, CallbackId, Msg, Request, RequestResponse}, request, send_msg, tag::TagHandle, - CALLBACK_VEC, + CallbackVec, }; /// A unique identifier for an output. @@ -58,6 +58,10 @@ pub fn get_focused() -> Option { /// 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. /// +/// `func` takes in two parameters: +/// - `0`: An [`OutputHandle`] you can act on. +/// - `1`: A `&mut `[`CallbackVec`] for use in the closure. +/// /// 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`. @@ -66,19 +70,18 @@ pub fn get_focused() -> Option { /// 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(mut func: F) +pub fn connect_for_all<'a, F>(mut func: F, callback_vec: &mut CallbackVec<'a>) where - F: FnMut(OutputHandle) + Send + 'static, + F: FnMut(OutputHandle, &mut CallbackVec) + 'a, { - let args_callback = move |args: Option| { + let args_callback = move |args: Option, callback_vec: &mut CallbackVec<'_>| { if let Some(Args::ConnectForAllOutputs { output_name }) = args { - func(OutputHandle(OutputName(output_name))); + func(OutputHandle(OutputName(output_name)), callback_vec); } }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let len = callback_vec.len(); - callback_vec.push(Box::new(args_callback)); + let len = callback_vec.callbacks.len(); + callback_vec.callbacks.push(Box::new(args_callback)); let msg = Msg::ConnectForAllOutputs { callback_id: CallbackId(len as u32), diff --git a/api/rust/src/process.rs b/api/rust/src/process.rs index c6c3830..3f7de02 100644 --- a/api/rust/src/process.rs +++ b/api/rust/src/process.rs @@ -2,7 +2,7 @@ use crate::{ msg::{Args, CallbackId, Msg}, - send_msg, CALLBACK_VEC, + send_msg, CallbackVec, }; /// Spawn a process. @@ -26,11 +26,18 @@ pub fn spawn(command: Vec<&str>) -> anyhow::Result<()> { /// - `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(command: Vec<&str>, mut callback: F) -> anyhow::Result<()> +/// - `4`: A `&mut `[`CallbackVec`] for use inside the closure. +/// +/// You must also pass in a mutable reference to a [`CallbackVec`] in order to store your callback. +pub fn spawn_with_callback<'a, F>( + command: Vec<&str>, + mut callback: F, + callback_vec: &mut CallbackVec<'a>, +) -> anyhow::Result<()> where - F: FnMut(Option, Option, Option, Option) + Send + 'static, + F: FnMut(Option, Option, Option, Option, &mut CallbackVec) + 'a, { - let args_callback = move |args: Option| { + let args_callback = move |args: Option, callback_vec: &mut CallbackVec<'_>| { if let Some(Args::Spawn { stdout, stderr, @@ -38,13 +45,12 @@ where exit_msg, }) = args { - callback(stdout, stderr, exit_code, exit_msg); + callback(stdout, stderr, exit_code, exit_msg, callback_vec); } }; - let mut callback_vec = CALLBACK_VEC.lock().unwrap(); - let len = callback_vec.len(); - callback_vec.push(Box::new(args_callback)); + let len = callback_vec.callbacks.len(); + callback_vec.callbacks.push(Box::new(args_callback)); let msg = Msg::Spawn { command: command.into_iter().map(|s| s.to_string()).collect(),