mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-13 08:01:05 +01:00
Transition from struct methods to module functions
This commit is contained in:
parent
788910e503
commit
0e32f52972
11 changed files with 515 additions and 546 deletions
|
@ -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());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
},
|
||||
|
|
|
@ -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
66
api/rust/src/pinnacle.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue