Add tag and some other stuff

This commit is contained in:
Ottatop 2023-10-19 19:26:12 -05:00
parent c62d090f9f
commit cd602fee09
4 changed files with 211 additions and 12 deletions

View file

@ -2,7 +2,10 @@ use pinnacle_api::{Modifier, MouseButton, MouseEdge};
fn main() {
pinnacle_api::setup(|pinnacle| {
pinnacle.process.spawn(vec!["alacritty"]).unwrap();
pinnacle.output.connect_for_all(move |output| {
pinnacle.tag.add(&output, &["1", "2", "3", "4", "5"]);
pinnacle.tag.get("1", Some(&output)).unwrap().toggle();
});
pinnacle.input.keybind(&[Modifier::Ctrl], 'a', move || {
pinnacle.process.spawn(vec!["alacritty"]).unwrap();

View file

@ -2,6 +2,7 @@ mod input;
mod msg;
mod output;
mod process;
mod tag;
mod window;
use input::Input;
@ -9,6 +10,7 @@ pub use input::MouseButton;
pub use msg::Modifier;
pub use msg::MouseEdge;
use output::Output;
use tag::Tag;
use window::Window;
pub use xkbcommon::xkb::keysyms;
pub use xkbcommon::xkb::Keysym;
@ -49,6 +51,7 @@ pub fn setup(config_func: impl FnOnce(Pinnacle)) -> anyhow::Result<()> {
input: Input,
window: Window,
output: Output,
tag: Tag,
};
config_func(pinnacle);
@ -174,4 +177,5 @@ pub struct Pinnacle {
pub window: Window,
pub input: Input,
pub output: Output,
pub tag: Tag,
}

View file

@ -1,6 +1,8 @@
use crate::{
msg::{OutputName, Request, RequestResponse},
request,
msg::{Args, CallbackId, Msg, OutputName, Request, RequestResponse},
request, send_msg,
tag::TagHandle,
CALLBACK_VEC,
};
/// Output management.
@ -46,6 +48,27 @@ impl Output {
.map(|s| OutputHandle(OutputName(s)))
.find(|op| op.properties().focused == Some(true))
}
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 msg = Msg::ConnectForAllOutputs {
callback_id: CallbackId(len as u32),
};
send_msg(msg).unwrap();
}
}
/// An output handle.
@ -53,31 +76,33 @@ impl Output {
/// This is a handle to one of your monitors.
/// It serves to make it easier to deal with them, defining methods for getting properties and
/// helpers for things like positioning multiple monitors.
pub struct OutputHandle(OutputName);
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct OutputHandle(pub OutputName);
/// Properties of an output.
pub struct OutputProperties {
/// The make.
make: Option<String>,
pub make: Option<String>,
/// The model.
///
/// This is something like `27GL850` or whatever gibberish monitor manufacturers name their
/// displays.
model: Option<String>,
pub model: Option<String>,
/// The location of the output in the global space.
loc: Option<(i32, i32)>,
pub loc: Option<(i32, i32)>,
/// The resolution of the output in pixels, where `res.0` is the width and `res.1` is the
/// height.
res: Option<(i32, i32)>,
pub res: Option<(i32, i32)>,
/// The refresh rate of the output in millihertz.
///
/// For example, 60Hz is returned as 60000.
refresh_rate: Option<i32>,
pub refresh_rate: Option<i32>,
/// The physical size of the output in millimeters.
physical_size: Option<(i32, i32)>,
pub physical_size: Option<(i32, i32)>,
/// Whether or not the output is focused.
focused: Option<bool>,
// TODO: tags
pub focused: Option<bool>,
/// The tags on this output.
pub tags: Vec<TagHandle>,
}
impl OutputHandle {
@ -108,6 +133,11 @@ impl OutputHandle {
refresh_rate,
physical_size,
focused,
tags: tag_ids
.unwrap_or(vec![])
.into_iter()
.map(TagHandle)
.collect(),
}
}
}

162
api/rust/src/tag.rs Normal file
View file

@ -0,0 +1,162 @@
use std::collections::HashMap;
use crate::{
msg::{Layout, Msg, OutputName, Request, RequestResponse, TagId},
output::{Output, OutputHandle},
request, send_msg,
};
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| Some(&op) == output)
})
.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(TagHandle)
}
// 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(),
};
send_msg(msg).unwrap();
}
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 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;
}
}
}
tag.set_layout(layouts[*index]);
};
LayoutCycler {
cycle: Box::new(cycle),
}
}
}
/// Which direction to cycle layouts.
#[derive(Debug, Clone, Copy)]
enum Cycle {
/// Cycle layouts forward.
Forward,
/// Cycle layouts backward.
Backward,
}
/// A layout cycler that keeps track of tags and their layouts and provides methods to cycle
/// layouts on them.
#[allow(clippy::type_complexity)]
pub struct LayoutCycler {
cycle: Box<dyn FnMut(Cycle, Option<&OutputHandle>)>,
}
impl LayoutCycler {
pub fn next(&mut self, output: Option<&OutputHandle>) {
(self.cycle)(Cycle::Forward, output);
}
pub fn prev(&mut self, output: Option<&OutputHandle>) {
(self.cycle)(Cycle::Backward, output);
}
}
pub struct TagHandle(pub TagId);
pub struct TagProperties {
active: Option<bool>,
name: Option<String>,
output: Option<OutputHandle>,
}
impl TagHandle {
pub fn properties(&self) -> TagProperties {
let RequestResponse::TagProps {
active,
name,
output_name,
} = request(Request::GetTagProps { tag_id: self.0 })
else {
unreachable!()
};
TagProperties {
active,
name,
output: output_name.map(|name| OutputHandle(OutputName(name))),
}
}
pub fn toggle(&self) {
let msg = Msg::ToggleTag { tag_id: self.0 };
send_msg(msg).unwrap();
}
pub fn switch_to(&self) {
let msg = Msg::SwitchToTag { tag_id: self.0 };
send_msg(msg).unwrap();
}
pub fn set_layout(&self, layout: Layout) {
let msg = Msg::SetLayout {
tag_id: self.0,
layout,
};
send_msg(msg).unwrap()
}
}