Update output in API, modify tag tracking

This commit is contained in:
Ottatop 2023-07-11 16:10:31 -05:00 committed by Ottatop
parent d91c06dbe9
commit 3efdb9d73f
13 changed files with 251 additions and 94 deletions

View file

@ -62,8 +62,8 @@ require("pinnacle").setup(function(pinnacle)
output.connect_for_all(function(op)
tag.add(op, "1", "2", "3", "4", "5")
tag.toggle("1", op)
end)
tag.toggle("1")
input.keybind({ mod_key }, keys.KEY_1, function()
tag.switch_to("1")

View file

@ -19,8 +19,8 @@
---@field Spawn { command: string[], callback_id: integer? }
---@field Request Request
--Tags
---@field ToggleTag { tag_id: string }
---@field SwitchToTag { tag_id: string }
---@field ToggleTag { output_name: string, tag_name: string }
---@field SwitchToTag { output_name: string, tag_name: string }
---@field AddTags { output_name: string, tags: string[] }
---@field RemoveTags { output_name: string, tags: string[] }
--Outputs
@ -39,7 +39,7 @@
---@field GetOutputsByModel { model: string }
---@field GetOutputsByRes { res: integer[] }
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows"
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus"
---@class IncomingMsg
---@field CallCallback { callback_id: integer, args: Args }

View file

@ -57,7 +57,7 @@ function output.get_by_name(name)
end
end
---NOTE: This may or may not be what is reported by other monitor listing utilities. One of my monitors fails to report itself in Smithay when it is correctly picked up by tools like wlr-randr. I'll fix this in the future.
---NOTE: This may or may not be what is reported by other monitor listing utilities. Pinnacle currently fails to pick up one of my monitors' models when it is correctly picked up by tools like wlr-randr. I'll fix this in the future.
---
---Get outputs by their model.
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
@ -112,10 +112,31 @@ function output.get_by_res(width, height)
return outputs
end
---Get the currently focused output. This is currently the one with the cursor on it.
---@return Output|nil output The output, or nil if none are focused.
function output.get_focused()
SendMsg({
Request = "GetOutputByFocus",
})
local response = ReadMsg()
local names = response.RequestResponse.response.Outputs.names
if names[1] ~= nil then
return new_output({ name = names[1] })
else
return nil
end
end
---Connect a function to be run on all current and future outputs.
---
---When called, `connect_for_all` will immediately run `func` with all currently connected outputs.
---If a new output is connected, `func` will also be called with it.
---
---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.
---@param func fun(output: Output) The function that will be run.
function output.connect_for_all(func)
---@param args Args

View file

@ -54,26 +54,42 @@ function tag.add_table(output, tags)
})
end
---Toggle a tag on the currently focused output.
---Toggle a tag on the specified output. If `output` isn't specified, toggle it on the currently focused output instead.
---
---# Example
---
---```lua
----- Assuming all tags are toggled off...
---tag.toggle("1")
---tag.toggle("2")
---local op = output.get_by_name("DP-1")
---tag.toggle("1", op)
---tag.toggle("2", op)
----- will cause windows on both tags 1 and 2 to be displayed at the same time.
---```
---@param name string The name of the tag.
function tag.toggle(name)
SendMsg({
ToggleTag = {
tag_id = name,
},
})
---@param output Output? The output.
function tag.toggle(name, output)
if output ~= nil then
SendMsg({
ToggleTag = {
output_name = output.name,
tag_name = name,
},
})
else
local op = require("output").get_focused()
if op ~= nil then
SendMsg({
ToggleTag = {
output_name = op.name,
tag_name = name,
},
})
end
end
end
---Switch to a tag on the currently focused output, deactivating any other active tags on that output.
---Switch to a tag on the specified output, deactivating any other active tags on it.
---If `output` is not specified, this uses the currently focused output instead.
---
---This is used to replicate what a traditional workspace is on some other Wayland compositors.
---
@ -83,12 +99,26 @@ end
---tag.switch_to("3") -- Switches to and displays *only* windows on tag 3
---```
---@param name string The name of the tag.
function tag.switch_to(name)
SendMsg({
SwitchToTag = {
tag_id = name,
},
})
---@param output Output? The output.
function tag.switch_to(name, output)
if output ~= nil then
SendMsg({
SwitchToTag = {
output_name = output.name,
tag_name = name,
},
})
else
local op = require("output").get_focused()
if op ~= nil then
SendMsg({
SwitchToTag = {
output_name = op.name,
tag_name = name,
},
})
end
end
end
return tag

View file

@ -49,11 +49,13 @@ pub enum Msg {
// Tag management
// FIXME: tag_id should not be a string
ToggleTag {
tag_id: String,
output_name: String,
tag_name: String,
},
// FIXME: tag_id should not be a string
SwitchToTag {
tag_id: String,
output_name: String,
tag_name: String,
},
AddTags {
/// The name of the output you want these tags on.
@ -99,6 +101,7 @@ pub enum Request {
GetOutputByName { name: String },
GetOutputsByModel { model: String },
GetOutputsByRes { res: (u32, u32) },
GetOutputByFocus,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]

View file

@ -219,7 +219,7 @@ pub fn run_winit() -> Result<(), Box<dyn Error>> {
None,
None,
);
state.re_layout();
state.re_layout(&output);
}
WinitEvent::Focus(_) => {}
WinitEvent::Input(input_evt) => {

View file

@ -232,12 +232,12 @@ impl<B: Backend> XdgShellHandler for State<B> {
(Some(output), _) | (None, Some(output)) => output.with_state(|state| {
let output_tags = state
.focused_tags()
.map(|tag| tag.id.clone())
.map(|tag| tag.clone())
.collect::<Vec<_>>();
if !output_tags.is_empty() {
output_tags
} else if let Some(first_tag) = state.tags.first() {
vec![first_tag.id.clone()]
vec![first_tag.clone()]
} else {
vec![]
}
@ -267,7 +267,10 @@ impl<B: Backend> XdgShellHandler for State<B> {
focused_output.with_state(|state| {
data.state
.windows
.to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect())
.to_master_stack(
focused_output,
state.focused_tags().map(|tag| tag.clone()).collect(),
)
.layout(&data.state.space, focused_output);
});
}
@ -280,7 +283,10 @@ impl<B: Backend> XdgShellHandler for State<B> {
if let Some(focused_output) = self.focus_state.focused_output.as_ref() {
focused_output.with_state(|state| {
self.windows
.to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect())
.to_master_stack(
focused_output,
state.focused_tags().map(|tag| tag.clone()).collect(),
)
.layout(&self.space, focused_output);
});
}

View file

@ -13,7 +13,7 @@ use smithay::{
use crate::{
backend::Backend,
state::{State, WithState},
tag::TagId,
tag::Tag,
window::window_state::WindowResizeState,
};
@ -30,6 +30,7 @@ pub trait Layout<S: SpaceElement> {
pub struct MasterStack<S: SpaceElement> {
inner: Vec<S>,
output: Output,
}
impl MasterStack<Window> {
@ -49,6 +50,8 @@ impl MasterStack<Window> {
return;
};
let output_loc = output.current_location();
let height = output_geo.size.h / stack_count as i32;
for (i, win) in self.stack().enumerate() {
@ -59,7 +62,11 @@ impl MasterStack<Window> {
win.with_state(|state| {
state.resize_state = WindowResizeState::WaitingForAck(
win.toplevel().send_configure(),
(output_geo.size.w / 2, i as i32 * height).into(),
(
output_geo.size.w / 2 + output_loc.x,
i as i32 * height + output_loc.y,
)
.into(),
);
});
}
@ -77,6 +84,8 @@ impl Layout<Window> for MasterStack<Window> {
return;
};
let output_loc = output.current_location();
if self.stack().count() == 0 {
// one window
master.toplevel().with_pending_state(|state| {
@ -86,7 +95,7 @@ impl Layout<Window> for MasterStack<Window> {
master.with_state(|state| {
state.resize_state = WindowResizeState::WaitingForAck(
master.toplevel().send_configure(),
(0, 0).into(),
(output_loc.x, output_loc.y).into(),
);
});
} else {
@ -98,7 +107,7 @@ impl Layout<Window> for MasterStack<Window> {
master.with_state(|state| {
state.resize_state = WindowResizeState::WaitingForAck(
master.toplevel().send_configure(),
(0, 0).into(),
(output_loc.x, output_loc.y).into(),
);
});
@ -109,6 +118,7 @@ impl Layout<Window> for MasterStack<Window> {
pub struct Dwindle<S: SpaceElement> {
inner: Vec<S>,
output: Output,
}
impl Layout<Window> for Dwindle<Window> {
@ -119,32 +129,34 @@ impl Layout<Window> for Dwindle<Window> {
pub trait LayoutVec<S: SpaceElement> {
/// Interpret this vec as a master-stack layout.
fn to_master_stack(&self, tags: Vec<TagId>) -> MasterStack<S>;
fn to_dwindle(&self, tags: Vec<TagId>) -> Dwindle<S>;
fn to_master_stack(&self, output: &Output, tags: Vec<Tag>) -> MasterStack<S>;
fn to_dwindle(&self, output: &Output, tags: Vec<Tag>) -> Dwindle<S>;
}
impl LayoutVec<Window> for Vec<Window> {
fn to_master_stack(&self, tags: Vec<TagId>) -> MasterStack<Window> {
fn to_master_stack(&self, output: &Output, tags: Vec<Tag>) -> MasterStack<Window> {
MasterStack {
inner: filter_windows(self, tags),
output: output.clone(), // TODO: get rid of?
}
}
fn to_dwindle(&self, tags: Vec<TagId>) -> Dwindle<Window> {
fn to_dwindle(&self, output: &Output, tags: Vec<Tag>) -> Dwindle<Window> {
Dwindle {
inner: filter_windows(self, tags),
output: output.clone(),
}
}
}
fn filter_windows(windows: &[Window], tags: Vec<TagId>) -> Vec<Window> {
fn filter_windows(windows: &[Window], tags: Vec<Tag>) -> Vec<Window> {
windows
.iter()
.filter(|window| {
window.with_state(|state| {
state.floating.is_tiled() && {
for tag_id in state.tags.iter() {
if tags.iter().any(|tag| tag == tag_id) {
for tag in state.tags.iter() {
if tags.iter().any(|tg| tg == tag) {
return true;
}
}

View file

@ -10,6 +10,9 @@ use smithay::output::Output;
use crate::{state::WithState, tag::Tag};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct OutputName(pub String);
#[derive(Default)]
pub struct OutputState {
pub tags: Vec<Tag>,
@ -36,6 +39,6 @@ impl WithState for Output {
impl OutputState {
pub fn focused_tags(&mut self) -> impl Iterator<Item = &mut Tag> {
self.tags.iter_mut().filter(|tag| tag.active)
self.tags.iter_mut().filter(|tag| tag.active())
}
}

View file

@ -162,15 +162,16 @@ impl<B: Backend> State<B> {
.as_ref()
.unwrap()
.with_state(|op_state| {
let tag = op_state.tags.iter().find(|tag| tag.name == tag_id);
let tag = op_state.tags.iter().find(|tag| tag.name() == tag_id);
if let Some(tag) = tag {
state.tags = vec![tag.id.clone()];
state.tags = vec![tag.clone()];
}
});
});
}
self.re_layout();
let output = self.focus_state.focused_output.clone().unwrap();
self.re_layout(&output);
}
Msg::ToggleTagOnWindow { window_id, tag_id } => {
if let Some(window) = self
@ -184,64 +185,66 @@ impl<B: Backend> State<B> {
.as_ref()
.unwrap()
.with_state(|op_state| {
let tag = op_state.tags.iter().find(|tag| tag.name == tag_id);
let tag = op_state.tags.iter().find(|tag| tag.name() == tag_id);
if let Some(tag) = tag {
if state.tags.contains(&tag.id) {
state.tags.retain(|id| id != &tag.id);
if state.tags.contains(&tag) {
state.tags.retain(|tg| tg != tag);
} else {
state.tags.push(tag.id.clone());
state.tags.push(tag.clone());
}
}
});
});
self.re_layout();
let output = self.focus_state.focused_output.clone().unwrap();
self.re_layout(&output);
}
}
Msg::ToggleTag { tag_id } => {
self.focus_state
.focused_output
.as_ref()
.unwrap()
.with_state(|state| {
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) {
tag.active = !tag.active;
Msg::ToggleTag { output_name, tag_name } => {
tracing::debug!("ToggleTag");
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
if let Some(output) = output {
output.with_state(|state| {
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) {
tracing::debug!("Setting tag {tag:?} to {}", !tag.active());
tag.set_active(!tag.active());
}
});
self.re_layout();
self.re_layout(&output);
}
}
Msg::SwitchToTag { tag_id } => {
self.focus_state
.focused_output
.as_ref()
.unwrap()
.with_state(|state| {
if !state.tags.iter().any(|tag| tag.name == tag_id) {
Msg::SwitchToTag { output_name, tag_name } => {
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
if let Some(output) = output {
output.with_state(|state| {
if !state.tags.iter().any(|tag| tag.name() == tag_name) {
// TODO: notify error
return;
}
for tag in state.tags.iter_mut() {
tag.active = false;
tag.set_active(false);
}
let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) else {
unreachable!()
};
tag.active = true;
let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) else {
unreachable!()
};
tag.set_active(true);
tracing::debug!(
"focused tags: {:?}",
state
.tags
.iter()
.filter(|tag| tag.active)
.map(|tag| &tag.name)
.filter(|tag| tag.active())
.map(|tag| tag.name())
.collect::<Vec<_>>()
);
});
self.re_layout();
self.re_layout(&output);
}
}
// TODO: add output
Msg::AddTags { output_name, tags } => {
@ -251,7 +254,10 @@ impl<B: Backend> State<B> {
.find(|output| output.name() == output_name)
{
output.with_state(|state| {
state.tags.extend(tags.iter().cloned().map(Tag::new));
state
.tags
.extend(tags.iter().cloned().map(Tag::new));
tracing::debug!("tags added, are now {:?}", state.tags);
});
}
}
@ -262,7 +268,7 @@ impl<B: Backend> State<B> {
.find(|output| output.name() == output_name)
{
output.with_state(|state| {
state.tags.retain(|tag| !tags.contains(&tag.name));
state.tags.retain(|tag| !tags.contains(&tag.name()));
});
}
}
@ -456,6 +462,28 @@ impl<B: Backend> State<B> {
)
.unwrap();
}
Request::GetOutputByFocus => {
let names = self
.focus_state
.focused_output
.as_ref()
.map(|output| output.name())
.into_iter()
.collect::<Vec<_>>();
let stream = self
.api_state
.stream
.as_ref()
.expect("Stream doesn't exist");
let mut stream = stream.lock().expect("Couldn't lock stream");
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::Outputs { names },
},
)
.unwrap();
}
},
}
}
@ -608,19 +636,25 @@ impl<B: Backend> State<B> {
}
}
pub fn re_layout(&mut self) {
let output = self.focus_state.focused_output.as_ref().unwrap();
pub fn re_layout(&mut self, output: &Output) {
let windows = self.windows.iter().filter(|win| {
win.with_state(|state| state.tags.iter().any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output)))
}).cloned().collect::<Vec<_>>();
let (render, do_not_render) = output.with_state(|state| {
self.windows
.to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect())
.to_master_stack(
output,
state.focused_tags().map(|tag| tag.clone()).collect(),
)
.layout(&self.space, output);
self.windows.iter().cloned().partition::<Vec<_>, _>(|win| {
windows.iter().cloned().partition::<Vec<_>, _>(|win| {
win.with_state(|win_state| {
if win_state.floating.is_floating() {
return true;
}
for tag_id in win_state.tags.iter() {
if state.focused_tags().any(|tag| &tag.id == tag_id) {
for tag in win_state.tags.iter() {
if state.focused_tags().any(|tg| tg == tag) {
return true;
}
}

View file

@ -5,13 +5,22 @@
// SPDX-License-Identifier: MPL-2.0
use std::{
cell::RefCell,
hash::Hash,
rc::Rc,
sync::atomic::{AtomicU32, Ordering},
};
use smithay::output::Output;
use crate::{
backend::Backend,
state::{State, WithState},
};
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct TagId(u32);
impl TagId {
@ -21,22 +30,60 @@ impl TagId {
}
#[derive(Debug)]
pub struct Tag {
struct TagInner {
/// The internal id of this tag.
pub id: TagId,
id: TagId,
/// The name of this tag.
pub name: String,
name: String,
/// Whether this tag is active or not.
pub active: bool,
active: bool,
// TODO: layout
}
impl PartialEq for TagInner {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for TagInner {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tag(Rc<RefCell<TagInner>>);
impl Tag {
pub fn id(&self) -> TagId {
self.0.borrow().id
}
pub fn name(&self) -> String {
self.0.borrow().name.clone()
}
pub fn active(&self) -> bool {
self.0.borrow().active
}
pub fn set_active(&mut self, active: bool) {
self.0.borrow_mut().active = active;
}
}
impl Tag {
pub fn new(name: String) -> Self {
Self {
Self(Rc::new(RefCell::new(TagInner {
id: TagId::next(),
name,
active: false,
}
})))
}
}
impl<B: Backend> State<B> {
pub fn output_for_tag(&self, tag: &Tag) -> Option<Output> {
self.space
.outputs()
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == tag)))
.cloned()
}
}

View file

@ -62,7 +62,8 @@ pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &Window) {
}
});
state.re_layout();
let output = state.focus_state.focused_output.clone().unwrap();
state.re_layout(&output);
let output = state.focus_state.focused_output.as_ref().unwrap();
let render = output.with_state(|op_state| {
@ -75,8 +76,8 @@ pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &Window) {
if win_state.floating.is_floating() {
return true;
}
for tag_id in win_state.tags.iter() {
if op_state.focused_tags().any(|tag| &tag.id == tag_id) {
for tag in win_state.tags.iter() {
if op_state.focused_tags().any(|tg| tg == tag) {
return true;
}
}

View file

@ -14,7 +14,7 @@ use smithay::{
utils::{Logical, Point, Serial, Size},
};
use crate::{state::WithState, tag::TagId};
use crate::{state::WithState, tag::Tag};
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WindowId(u32);
@ -36,7 +36,7 @@ pub struct WindowState {
/// The window's resize state. See [WindowResizeState] for more.
pub resize_state: WindowResizeState,
/// What tags the window is currently on.
pub tags: Vec<TagId>,
pub tags: Vec<Tag>,
}
/// The state of a window's resize operation.