From a3c71ef9d5683316f46c90a5a22801474b6f1b78 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Tue, 4 Jul 2023 21:27:23 -0500 Subject: [PATCH 01/27] Begin work on layout system and fleshing out tag system --- src/backend/winit.rs | 4 +- src/handlers.rs | 75 +++++++++++--- src/layout.rs | 223 +++++++++++++++++++++++++++++++++++++++- src/layout/automatic.rs | 4 +- src/output.rs | 4 +- src/state.rs | 217 ++++++-------------------------------- src/tag.rs | 3 +- src/window.rs | 4 +- 8 files changed, 324 insertions(+), 210 deletions(-) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 89c7ed9..090f100 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -47,7 +47,7 @@ use smithay::{ }; use crate::{ - layout::{Direction, Layout}, + layout::{Direction, Layouts}, render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, state::{CalloopData, State}, }; @@ -220,7 +220,7 @@ pub fn run_winit() -> Result<(), Box> { None, None, ); - Layout::master_stack( + Layouts::master_stack( state, state.space.elements().cloned().collect(), Direction::Left, diff --git a/src/handlers.rs b/src/handlers.rs index e76939b..d2de17f 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -47,7 +47,7 @@ use smithay::{ use crate::{ backend::Backend, - layout::Layout, + layout::{Layout, LayoutVec}, output::OutputState, state::{ClientState, State}, window::window_state::{WindowResizeState, WindowState}, @@ -96,7 +96,7 @@ impl CompositorHandler for State { } fn commit(&mut self, surface: &WlSurface) { - tracing::debug!("commit"); + // tracing::debug!("commit"); utils::on_commit_buffer_handler::(surface); @@ -228,12 +228,16 @@ impl XdgShellHandler for State { WindowState::with_state(&window, |state| { state.tags = if let Some(focused_output) = &self.focus_state.focused_output { OutputState::with(focused_output, |state| { - state - .focused_tags - .iter() - .filter_map(|(id, active)| active.then_some(id.clone())) - .collect() - }) + let output_tags: Vec = + state.focused_tags.iter().cloned().collect(); + if !output_tags.is_empty() { + output_tags + } else if let Some(first_tag) = self.tag_state.tags.first() { + vec![first_tag.id.clone()] + } else { + vec![] + } + }) // TODO: repetition } else if let Some(first_tag) = self.tag_state.tags.first() { vec![first_tag.id.clone()] } else { @@ -244,6 +248,7 @@ impl XdgShellHandler for State { self.windows.push(window.clone()); self.space.map_element(window.clone(), (0, 0), true); + let clone = window.clone(); self.loop_handle.insert_idle(move |data| { data.state .seat @@ -251,25 +256,61 @@ impl XdgShellHandler for State { .expect("Seat had no keyboard") // FIXME: actually handle error .set_focus( &mut data.state, - Some(window.toplevel().wl_surface().clone()), + Some(clone.toplevel().wl_surface().clone()), SERIAL_COUNTER.next_serial(), ); }); - let windows: Vec = self.space.elements().cloned().collect(); - - self.loop_handle.insert_idle(|data| { - tracing::debug!("Layout master_stack"); - Layout::master_stack(&mut data.state, windows, crate::layout::Direction::Left); + self.loop_handle.insert_idle(move |data| { + if let Some(focused_output) = &data.state.focus_state.focused_output { + OutputState::with(focused_output, |state| { + if let Some(id) = state.focused_tags.iter().next() { + // TODO: make it work with more than one active tag + let tag = data + .state + .tag_state + .tags + .iter_mut() + .find(|tag| &tag.id == id) + .unwrap(); + tag.windows.as_master_stack().add( + &data.state.space, + focused_output, + window.clone(), + ); + } + }); + } }); } fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { tracing::debug!("toplevel destroyed"); + let window = self + .windows + .iter() + .find(|&win| win.toplevel() == &surface) + .unwrap(); + if let Some(focused_output) = self.focus_state.focused_output.as_ref() { + OutputState::with(focused_output, |state| { + if let Some(id) = state.focused_tags.iter().next() { + // TODO: make it work with more than one active tag + let tag = self + .tag_state + .tags + .iter_mut() + .find(|tag| &tag.id == id) + .unwrap(); + tag.windows + .as_master_stack() + .remove(&self.space, focused_output, window); + } + }); + } self.windows.retain(|window| window.toplevel() != &surface); - let mut windows: Vec = self.space.elements().cloned().collect(); - windows.retain(|window| window.toplevel() != &surface); - Layout::master_stack(self, windows, crate::layout::Direction::Left); + // let mut windows: Vec = self.space.elements().cloned().collect(); + // windows.retain(|window| window.toplevel() != &surface); + // Layouts::master_stack(self, windows, crate::layout::Direction::Left); let focus = self .focus_state .current_focus() diff --git a/src/layout.rs b/src/layout.rs index cba6a0f..919bf6b 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,10 +4,18 @@ // // SPDX-License-Identifier: MPL-2.0 +use smithay::{ + desktop::{space::SpaceElement, Space, Window}, + output::Output, + utils::{Logical, Size}, +}; + +use crate::window::window_state::{WindowResizeState, WindowState}; + pub mod automatic; pub mod manual; -pub struct Layout; +pub struct Layouts; pub enum Direction { Left, @@ -15,3 +23,216 @@ pub enum Direction { Top, Bottom, } + +pub struct MasterStack<'a, S: SpaceElement> { + inner: &'a mut Vec, +} + +pub trait Layout { + /// Add a [`SpaceElement`] to this layout and update positions. + fn add(&mut self, space: &Space, output: &Output, elem: S); + /// Remove a [`SpaceElement`] from this layout and update positions. + fn remove(&mut self, space: &Space, output: &Output, elem: &S); + + // TODO: return result + /// Swap two elements in this layout and update their positions. + fn swap(&mut self, space: &Space, elem1: &S, elem2: &S); + + /// Perform a full layout with all elements. Use this when you are switching from another layout. + fn layout(&self, space: &Space, output: &Output); +} + +impl MasterStack<'_, S> { + pub fn master(&self) -> Option<&S> { + self.inner.first() + } + + pub fn stack(&self) -> impl Iterator { + self.inner.iter().skip(1) + } +} + +impl MasterStack<'_, Window> { + fn layout_stack(&self, space: &Space, output: &Output) { + let stack_count = self.stack().count(); + + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + let height = output_geo.size.h / stack_count as i32; + + for (i, win) in self.stack().enumerate() { + win.toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, height).into()); + }); + + WindowState::with_state(win, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + win.toplevel().send_configure(), + (output_geo.size.w / 2, i as i32 * height).into(), + ); + }); + } + } +} + +pub fn swap_window_positions(space: &Space, win1: &Window, win2: &Window) { + // FIXME: moving the mouse quickly will break swapping + + let win1_loc = space.element_location(win1).unwrap(); // TODO: handle unwraps + let win2_loc = space.element_location(win2).unwrap(); + let win1_geo = win1.geometry(); + let win2_geo = win2.geometry(); + + win1.toplevel().with_pending_state(|state| { + state.size = Some(win2_geo.size); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some(win1_geo.size); + }); + + let serial = win1.toplevel().send_configure(); + WindowState::with_state(win1, |state| { + state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); + }); + + let serial = win2.toplevel().send_configure(); + WindowState::with_state(win2, |state| { + state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); + }); +} + +impl Layout for MasterStack<'_, Window> { + fn add(&mut self, space: &Space, output: &Output, elem: Window) { + self.inner.push(elem); + + if self.stack().count() == 0 { + let Some(master) = self.master() else { unreachable!() }; + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + master.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + WindowState::with_state(master, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + } else if self.stack().count() == 1 { + let Some(master) = self.master() else { unreachable!() }; + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + master.toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, output_geo.size.h).into()); + }); + + WindowState::with_state(master, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + self.layout_stack(space, output); + } else { + self.layout_stack(space, output); + } + } + + fn remove(&mut self, space: &Space, output: &Output, elem: &Window) { + self.inner.retain(|el| el != elem); + + let Some(master) = self.master() else { return }; + + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + if self.stack().count() == 0 { + master.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + WindowState::with_state(master, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + } else { + self.layout_stack(space, output); + } + } + + fn swap(&mut self, space: &Space, elem1: &Window, elem2: &Window) { + let mut elems = self.inner.iter_mut(); + let first = elems.find(|elem| *elem == elem1); + let second = elems.find(|elem| *elem == elem2); + if let Some(first) = first { + if let Some(second) = second { + std::mem::swap(first, second); + } + } + + swap_window_positions(space, elem1, elem2); + } + + fn layout(&self, space: &Space, output: &Output) { + let Some(master) = self.master() else { + return; + }; + + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + if self.stack().count() == 0 { + // one window + master.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + WindowState::with_state(master, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + } else { + let new_master_size: Size = + (output_geo.size.w / 2, output_geo.size.h).into(); + master.toplevel().with_pending_state(|state| { + state.size = Some(new_master_size); + }); + WindowState::with_state(master, |state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + + self.layout_stack(space, output); + } + } +} + +pub trait LayoutVec { + /// Interpret this vec as a master-stack layout. + fn as_master_stack(&mut self) -> MasterStack; + // fn as_binary_tree(&mut self); TODO: +} + +impl LayoutVec for Vec { + fn as_master_stack(&mut self) -> MasterStack { + MasterStack { inner: self } + } +} diff --git a/src/layout/automatic.rs b/src/layout/automatic.rs index 4d8b981..71d9179 100644 --- a/src/layout/automatic.rs +++ b/src/layout/automatic.rs @@ -15,9 +15,9 @@ use crate::{ window::window_state::{WindowResizeState, WindowState}, }; -use super::{Direction, Layout}; +use super::{Direction, Layouts}; -impl Layout { +impl Layouts { pub fn master_stack( state: &mut State, mut windows: Vec, diff --git a/src/output.rs b/src/output.rs index d440083..384f442 100644 --- a/src/output.rs +++ b/src/output.rs @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::{cell::RefCell, collections::HashMap}; +use std::{cell::RefCell, collections::HashSet}; use smithay::output::Output; @@ -12,7 +12,7 @@ use crate::tag::TagId; #[derive(Default)] pub struct OutputState { - pub focused_tags: HashMap, + pub focused_tags: HashSet, } impl OutputState { diff --git a/src/state.rs b/src/state.rs index a65498b..ca09a02 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,9 +18,8 @@ use crate::{ msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse}, PinnacleSocketSource, }, - backend::{udev::UdevData, winit::WinitData}, focus::FocusState, - layout::Layout, + layout::{Layout, LayoutVec}, output::OutputState, tag::{Tag, TagState}, window::{window_state::WindowState, WindowProperties}, @@ -179,16 +178,13 @@ impl State { Msg::ToggleTag { tag_id } => { OutputState::with( self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error - |state| match state.focused_tags.get_mut(&tag_id) { - Some(id) => { - *id = !*id; - tracing::debug!( - "toggled tag {tag_id:?} {}", - if *id { "on" } else { "off" } - ); - } - None => { - state.focused_tags.insert(tag_id.clone(), true); + |state| { + let should_remove = state.focused_tags.get(&tag_id).is_some(); + if should_remove { + state.focused_tags.remove(&tag_id); + tracing::debug!("toggled tag {tag_id:?} off"); + } else { + state.focused_tags.insert(tag_id.clone()); tracing::debug!("toggled tag {tag_id:?} on"); } }, @@ -198,24 +194,26 @@ impl State { } Msg::SwitchToTag { tag_id } => { OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - for (_, active) in state.focused_tags.iter_mut() { - *active = false; - } - if let Some(active) = state.focused_tags.get_mut(&tag_id) { - *active = true; - } else { - state.focused_tags.insert(tag_id.clone(), true); - } + state.focused_tags.clear(); + state.focused_tags.insert(tag_id.clone()); tracing::debug!("focused tags: {:?}", state.focused_tags); }); self.re_layout(); } Msg::AddTags { tags } => { - self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag { - id: tag, - windows: vec![], - })); + if let Some(output) = self + .focus_state + .focused_output + .as_ref() + .or_else(|| self.space.outputs().next()) + { + self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag { + id: tag, + windows: vec![], + output: output.clone(), + })); + } } Msg::RemoveTags { tags } => { self.tag_state.tags.retain(|tag| !tags.contains(&tag.id)); @@ -473,12 +471,12 @@ impl State { } pub fn re_layout(&mut self) { - let windows = + let mut windows = OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { for window in self.space.elements().cloned().collect::>() { let should_render = WindowState::with_state(&window, |win_state| { for tag_id in win_state.tags.iter() { - if *state.focused_tags.get(tag_id).unwrap_or(&false) { + if state.focused_tags.get(tag_id).is_some() { return true; } } @@ -494,7 +492,7 @@ impl State { .filter(|&win| { WindowState::with_state(win, |win_state| { for tag_id in win_state.tags.iter() { - if *state.focused_tags.get(tag_id).unwrap_or(&false) { + if state.focused_tags.get(tag_id).is_some() { return true; } } @@ -507,168 +505,21 @@ impl State { tracing::debug!("Laying out {} windows", windows.len()); - Layout::master_stack(self, windows, crate::layout::Direction::Left); + windows.as_master_stack().layout( + &self.space, + self.focus_state.focused_output.as_ref().unwrap(), + ); + + // Layouts::master_stack(self, windows, crate::layout::Direction::Left); } } -impl State { - /// Create the main [`State`]. - /// - /// This will set the WAYLAND_DISPLAY environment variable, insert Wayland necessary sources - /// into the event loop, and run an implementation of the config API (currently Lua). +impl State { pub fn init( - backend_data: WinitData, + backend_data: B, display: &mut Display, loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, CalloopData>, - ) -> Result> { - let socket = ListeningSocketSource::new_auto()?; - let socket_name = socket.socket_name().to_os_string(); - - std::env::set_var("WAYLAND_DISPLAY", socket_name.clone()); - - // Opening a new process will use up a few file descriptors, around 10 for Alacritty, for - // example. Because of this, opening up only around 100 processes would exhaust the file - // descriptor limit on my system (Arch btw) and cause a "Too many open files" crash. - // - // To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical - // environment, I *think* this is ok. - if let Err(err) = smithay::reexports::nix::sys::resource::setrlimit( - smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE, - 65536, - 65536 * 2, - ) { - tracing::error!("Could not raise fd limit: errno {err}"); - } - - loop_handle.insert_source(socket, |stream, _metadata, data| { - data.display - .handle() - .insert_client(stream, Arc::new(ClientState::default())) - .expect("Could not insert client into loop handle"); - })?; - - loop_handle.insert_source( - Generic::new( - display.backend().poll_fd().as_raw_fd(), - Interest::READ, - Mode::Level, - ), - |_readiness, _metadata, data| { - data.display.dispatch_clients(&mut data.state)?; - Ok(PostAction::Continue) - }, - )?; - - let (tx_channel, rx_channel) = calloop::channel::channel::(); - loop_handle.insert_source(rx_channel, |msg, _, data| match msg { - Event::Msg(msg) => data.state.handle_msg(msg), - Event::Closed => todo!(), - })?; - - // We want to replace the client if a new one pops up - // TODO: there should only ever be one client working at a time, and creating a new client - // | when one is already running should be impossible. - // INFO: this source try_clone()s the stream - loop_handle.insert_source(PinnacleSocketSource::new(tx_channel)?, |stream, _, data| { - if let Some(old_stream) = data - .state - .api_state - .stream - .replace(Arc::new(Mutex::new(stream))) - { - old_stream - .lock() - .expect("Couldn't lock old stream") - .shutdown(std::net::Shutdown::Both) - .expect("Couldn't shutdown old stream"); - } - })?; - - let (executor, sched) = - calloop::futures::executor::<()>().expect("Couldn't create executor"); - loop_handle.insert_source(executor, |_, _, _| {})?; - - // TODO: move all this into the lua api - let config_path = std::env::var("PINNACLE_CONFIG").unwrap_or_else(|_| { - let mut default_path = - std::env::var("XDG_CONFIG_HOME").unwrap_or("~/.config".to_string()); - default_path.push_str("/pinnacle/init.lua"); - default_path - }); - - if Path::new(&config_path).exists() { - let lua_path = std::env::var("LUA_PATH").expect("Lua is not installed!"); - let mut local_lua_path = std::env::current_dir() - .expect("Couldn't get current dir") - .to_string_lossy() - .to_string(); - local_lua_path.push_str("/api/lua"); // TODO: get from crate root and do dynamically - let new_lua_path = - format!("{local_lua_path}/?.lua;{local_lua_path}/?/init.lua;{local_lua_path}/lib/?.lua;{local_lua_path}/lib/?/init.lua;{lua_path}"); - - let lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!"); - let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}"); - - std::process::Command::new("lua5.4") - .arg(config_path) - .env("LUA_PATH", new_lua_path) - .env("LUA_CPATH", new_lua_cpath) - .spawn() - .expect("Could not start config process"); - } else { - tracing::error!("Could not find {}", config_path); - } - - let display_handle = display.handle(); - let mut seat_state = SeatState::new(); - let mut seat = seat_state.new_wl_seat(&display_handle, backend_data.seat_name()); - seat.add_pointer(); - seat.add_keyboard(XkbConfig::default(), 200, 25)?; - - Ok(Self { - backend_data, - loop_signal, - loop_handle, - clock: Clock::::new()?, - compositor_state: CompositorState::new::(&display_handle), - data_device_state: DataDeviceState::new::(&display_handle), - seat_state, - pointer_location: (0.0, 0.0).into(), - shm_state: ShmState::new::(&display_handle, vec![]), - space: Space::::default(), - cursor_status: CursorImageStatus::Default, - output_manager_state: OutputManagerState::new_with_xdg_output::(&display_handle), - xdg_shell_state: XdgShellState::new::(&display_handle), - viewporter_state: ViewporterState::new::(&display_handle), - fractional_scale_manager_state: FractionalScaleManagerState::new::( - &display_handle, - ), - input_state: InputState::new(), - api_state: ApiState::new(), - focus_state: FocusState::new(), - tag_state: TagState::new(), - - seat, - - move_mode: false, - socket_name: socket_name.to_string_lossy().to_string(), - - popup_manager: PopupManager::default(), - - async_scheduler: sched, - - windows: vec![], - }) - } -} - -impl State { - pub fn init( - backend_data: UdevData, - display: &mut Display, - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, CalloopData>, + loop_handle: LoopHandle<'static, CalloopData>, ) -> Result> { let socket = ListeningSocketSource::new_auto()?; let socket_name = socket.socket_name().to_os_string(); diff --git a/src/tag.rs b/src/tag.rs index d3ce220..b9b311c 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use smithay::desktop::Window; +use smithay::{desktop::Window, output::Output}; #[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] pub struct TagId(String); @@ -13,6 +13,7 @@ pub struct TagId(String); pub struct Tag { pub id: TagId, pub windows: Vec, + pub output: Output, // TODO: layout } diff --git a/src/window.rs b/src/window.rs index 9c80bb7..1c58b23 100644 --- a/src/window.rs +++ b/src/window.rs @@ -13,7 +13,7 @@ use smithay::{ }; use crate::{ - backend::Backend, layout::Layout, state::State, window::window_state::WindowResizeState, + backend::Backend, layout::Layouts, state::State, window::window_state::WindowResizeState, }; use self::window_state::{Float, WindowId, WindowState}; @@ -120,7 +120,7 @@ pub fn toggle_floating(state: &mut State, window: &Window) { }); let windows = state.space.elements().cloned().collect::>(); - Layout::master_stack(state, windows, crate::layout::Direction::Left); + Layouts::master_stack(state, windows, crate::layout::Direction::Left); state.space.raise_element(window, true); } From a14ce1ef2e6071aa566676641039005f8c0b9260 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Wed, 5 Jul 2023 16:03:02 -0500 Subject: [PATCH 02/27] Improve master stack layout and tags --- Cargo.toml | 1 + src/handlers.rs | 73 +++++++++++++++++++++++++++++++------------------ src/layout.rs | 41 +++++++++++++++++++-------- 3 files changed, 77 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5f6a56..0a6299a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ rmp-serde = { version = "1.1.1" } calloop = { version = "0.10.1", features = ["executor", "futures-io"] } futures-lite = { version = "1.13.0" } async-process = { version = "1.7.0" } +itertools = { version = "0.11.0" } [features] default = ["egl", "winit", "udev"] diff --git a/src/handlers.rs b/src/handlers.rs index d2de17f..03f5dc8 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -247,7 +247,7 @@ impl XdgShellHandler for State { }); self.windows.push(window.clone()); - self.space.map_element(window.clone(), (0, 0), true); + // self.space.map_element(window.clone(), (0, 0), true); let clone = window.clone(); self.loop_handle.insert_idle(move |data| { data.state @@ -264,20 +264,25 @@ impl XdgShellHandler for State { self.loop_handle.insert_idle(move |data| { if let Some(focused_output) = &data.state.focus_state.focused_output { OutputState::with(focused_output, |state| { - if let Some(id) = state.focused_tags.iter().next() { - // TODO: make it work with more than one active tag - let tag = data - .state - .tag_state - .tags - .iter_mut() - .find(|tag| &tag.id == id) - .unwrap(); - tag.windows.as_master_stack().add( - &data.state.space, - focused_output, - window.clone(), - ); + let window = window.clone(); + let mut tags = data + .state + .tag_state + .tags + .iter_mut() + .filter(|tg| state.focused_tags.contains(&tg.id)); + + if let Some(first) = tags.next() { + let mut layout = first.windows.as_master_stack(); + + for tg in tags { + layout = layout.chain_with(&mut tg.windows); + } + + layout.add(&data.state.space, focused_output, window); + } + for tag in data.state.tag_state.tags.iter() { + tracing::debug!("tag {:?}, {}", tag.id, tag.windows.len()); } }); } @@ -293,20 +298,36 @@ impl XdgShellHandler for State { .unwrap(); if let Some(focused_output) = self.focus_state.focused_output.as_ref() { OutputState::with(focused_output, |state| { - if let Some(id) = state.focused_tags.iter().next() { - // TODO: make it work with more than one active tag - let tag = self - .tag_state - .tags - .iter_mut() - .find(|tag| &tag.id == id) - .unwrap(); - tag.windows - .as_master_stack() - .remove(&self.space, focused_output, window); + let mut tags = self + .tag_state + .tags + .iter_mut() + .filter(|tg| state.focused_tags.contains(&tg.id)); + + if let Some(first) = tags.next() { + tracing::debug!("first tag: {:?}", first.id); + let mut layout = first.windows.as_master_stack(); + + for tg in tags { + tracing::debug!("tag: {:?}", tg.id); + layout = layout.chain_with(&mut tg.windows); + } + + // This will only remove the window from focused tags... + layout.remove(&self.space, focused_output, window); + } + + // ...so here we remove the window from any tag that isn't focused + for tag in self.tag_state.tags.iter_mut() { + tag.windows.retain(|el| el != window); } }); } + + for tag in self.tag_state.tags.iter() { + tracing::debug!("tag {:?}, {}", tag.id, tag.windows.len()); + } + self.windows.retain(|window| window.toplevel() != &surface); // let mut windows: Vec = self.space.elements().cloned().collect(); // windows.retain(|window| window.toplevel() != &surface); diff --git a/src/layout.rs b/src/layout.rs index 919bf6b..f978fd4 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: MPL-2.0 +use itertools::Itertools; use smithay::{ desktop::{space::SpaceElement, Space, Window}, output::Output, @@ -25,10 +26,10 @@ pub enum Direction { } pub struct MasterStack<'a, S: SpaceElement> { - inner: &'a mut Vec, + inner: Vec<&'a mut Vec>, } -pub trait Layout { +pub trait Layout<'a, S: SpaceElement> { /// Add a [`SpaceElement`] to this layout and update positions. fn add(&mut self, space: &Space, output: &Output, elem: S); /// Remove a [`SpaceElement`] from this layout and update positions. @@ -40,15 +41,21 @@ pub trait Layout { /// Perform a full layout with all elements. Use this when you are switching from another layout. fn layout(&self, space: &Space, output: &Output); + + fn chain_with(self, vec: &'a mut Vec) -> Self; } -impl MasterStack<'_, S> { - pub fn master(&self) -> Option<&S> { - self.inner.first() +impl MasterStack<'_, Window> { + pub fn master(&self) -> Option<&Window> { + self.inner.iter().flat_map(|vec| vec.iter()).next() } - pub fn stack(&self) -> impl Iterator { - self.inner.iter().skip(1) + pub fn stack(&self) -> impl Iterator { + self.inner + .iter() + .flat_map(|vec| vec.iter()) + .unique() + .skip(1) } } @@ -104,9 +111,11 @@ pub fn swap_window_positions(space: &Space, win1: &Window, win2: &Window }); } -impl Layout for MasterStack<'_, Window> { +impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { fn add(&mut self, space: &Space, output: &Output, elem: Window) { - self.inner.push(elem); + for vec in self.inner.iter_mut() { + vec.push(elem.clone()); + } if self.stack().count() == 0 { let Some(master) = self.master() else { unreachable!() }; @@ -147,7 +156,9 @@ impl Layout for MasterStack<'_, Window> { } fn remove(&mut self, space: &Space, output: &Output, elem: &Window) { - self.inner.retain(|el| el != elem); + for vec in self.inner.iter_mut() { + vec.retain(|el| el != elem); + } let Some(master) = self.master() else { return }; @@ -173,7 +184,7 @@ impl Layout for MasterStack<'_, Window> { } fn swap(&mut self, space: &Space, elem1: &Window, elem2: &Window) { - let mut elems = self.inner.iter_mut(); + let mut elems = self.inner.iter_mut().flat_map(|vec| vec.iter_mut()); let first = elems.find(|elem| *elem == elem1); let second = elems.find(|elem| *elem == elem2); if let Some(first) = first { @@ -223,6 +234,12 @@ impl Layout for MasterStack<'_, Window> { self.layout_stack(space, output); } } + + /// Chain another tag's windows to this one to be layed out. + fn chain_with(mut self, vec: &'a mut Vec) -> Self { + self.inner.push(vec); + self + } } pub trait LayoutVec { @@ -233,6 +250,6 @@ pub trait LayoutVec { impl LayoutVec for Vec { fn as_master_stack(&mut self) -> MasterStack { - MasterStack { inner: self } + MasterStack { inner: vec![self] } } } From 9cac9b1f2c7a879ad230004c600fa0225d8e33bc Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sat, 8 Jul 2023 21:26:04 -0500 Subject: [PATCH 03/27] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index de05670..5c7ace4 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Cool stuff happens on the dev branch sometimes, check it out! - [ ] Layout system - [ ] Server-side decorations - [ ] The other stuff Awesome has +- [ ] XWayland support - [x] Is very cool :thumbsup: ## Info From c7e273b7700a2cc407f36f24867e81d67fb55ddb Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sat, 8 Jul 2023 21:26:19 -0500 Subject: [PATCH 04/27] Add stylua.toml --- api/lua/stylua.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 api/lua/stylua.toml diff --git a/api/lua/stylua.toml b/api/lua/stylua.toml new file mode 100644 index 0000000..394e884 --- /dev/null +++ b/api/lua/stylua.toml @@ -0,0 +1 @@ +indent_type = "Spaces" From 170addbad19c5c2a300fd5e91f4d6f1186f941ea Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sat, 8 Jul 2023 21:30:09 -0500 Subject: [PATCH 05/27] Add apt update --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9b2c046..11525ed 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Get dependencies - run: sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgbm-dev libseat-dev libsystemd-dev + run: sudo apt update && sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgbm-dev libseat-dev libsystemd-dev - name: Build run: cargo build --verbose - name: Run tests that don't exist From d860172334c4e062b152c83e045f4fb1f0057d08 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sun, 9 Jul 2023 10:00:16 -0500 Subject: [PATCH 06/27] Clean up old layouts --- src/backend/winit.rs | 7 +- src/grab/move_grab.rs | 52 ++++++++---- src/handlers.rs | 19 +++-- src/layout.rs | 80 +++++++++++++++---- src/layout/automatic.rs | 160 ------------------------------------- src/layout/manual.rs | 7 -- src/output.rs | 2 +- src/state.rs | 90 ++++++++++----------- src/window.rs | 41 +--------- src/window/window_state.rs | 6 +- 10 files changed, 160 insertions(+), 304 deletions(-) delete mode 100644 src/layout/automatic.rs delete mode 100644 src/layout/manual.rs diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 090f100..3a03f83 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -47,7 +47,6 @@ use smithay::{ }; use crate::{ - layout::{Direction, Layouts}, render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, state::{CalloopData, State}, }; @@ -220,11 +219,7 @@ pub fn run_winit() -> Result<(), Box> { None, None, ); - Layouts::master_stack( - state, - state.space.elements().cloned().collect(), - Direction::Left, - ); + state.re_layout(); } WinitEvent::Focus(_) => {} WinitEvent::Input(input_evt) => { diff --git a/src/grab/move_grab.rs b/src/grab/move_grab.rs index 98c4740..4df0b37 100644 --- a/src/grab/move_grab.rs +++ b/src/grab/move_grab.rs @@ -19,7 +19,13 @@ use smithay::{ utils::{IsAlive, Logical, Point, Rectangle}, }; -use crate::{backend::Backend, state::State, window::window_state::WindowState}; +use crate::{ + backend::Backend, + layout::{Layout, LayoutVec}, + output::OutputState, + state::State, + window::window_state::{WindowResizeState, WindowState}, +}; pub struct MoveSurfaceGrab { pub start_data: GrabStartData, @@ -47,7 +53,7 @@ impl PointerGrab> for MoveSurfaceGrab> { // tracing::info!("window geo is: {:?}", self.window.geometry()); // tracing::info!("loc is: {:?}", data.space.element_location(&self.window)); - let tiled = WindowState::with_state(&self.window, |state| state.floating.is_tiled()); + let tiled = WindowState::with(&self.window, |state| state.floating.is_tiled()); if tiled { // INFO: this is being used instead of space.element_under(event.location) because that @@ -72,28 +78,46 @@ impl PointerGrab> for MoveSurfaceGrab> { return; } - let window_under_floating = - WindowState::with_state(&window_under, |state| state.floating.is_floating()); + let is_floating = + WindowState::with(&window_under, |state| state.floating.is_floating()); - if window_under_floating { + if is_floating { return; } - data.swap_window_positions(&self.window, &window_under); + let has_pending_resize = WindowState::with(&window_under, |state| { + !matches!(state.resize_state, WindowResizeState::Idle) + }); + + if has_pending_resize { + return; + } + + // data.swap_window_positions(&self.window, &window_under); + let output = data.focus_state.focused_output.as_ref().unwrap(); + OutputState::with(output, |state| { + let mut tags = data + .tag_state + .tags + .iter_mut() + .filter(|tg| state.tags.contains(&tg.id)); + + if let Some(first) = tags.next() { + let mut layout = first.windows.as_master_stack(); + + for tg in tags { + layout = layout.chain_with(&mut tg.windows); + } + + layout.swap(&data.space, &self.window, &window_under); + } + }) } } else { let delta = event.location - self.start_data.location; let new_loc = self.initial_window_loc.to_f64() + delta; data.space .map_element(self.window.clone(), new_loc.to_i32_round(), true); - // let loc = data - // .space - // .element_location(&self.window) - // .unwrap_or((0, 0).into()); - // tracing::info!("new loc from element_location: {}, {}", loc.x, loc.y); - // let geo = self.window.geometry(); - // tracing::info!("geo loc: {}, {}", geo.loc.x, geo.loc.y); - // tracing::info!("geo size: {}, {}", geo.size.w, geo.size.h); } } diff --git a/src/handlers.rs b/src/handlers.rs index 03f5dc8..faf7b90 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -117,7 +117,7 @@ impl CompositorHandler for State { crate::grab::resize_grab::handle_commit(self, surface); if let Some(window) = self.window_for_surface(surface) { - WindowState::with_state(&window, |state| { + WindowState::with(&window, |state| { if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state { state.resize_state = WindowResizeState::Idle; self.space.map_element(window.clone(), new_pos, false); @@ -225,11 +225,10 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new(surface); - WindowState::with_state(&window, |state| { + WindowState::with(&window, |state| { state.tags = if let Some(focused_output) = &self.focus_state.focused_output { OutputState::with(focused_output, |state| { - let output_tags: Vec = - state.focused_tags.iter().cloned().collect(); + let output_tags: Vec = state.tags.iter().cloned().collect(); if !output_tags.is_empty() { output_tags } else if let Some(first_tag) = self.tag_state.tags.first() { @@ -270,7 +269,7 @@ impl XdgShellHandler for State { .tag_state .tags .iter_mut() - .filter(|tg| state.focused_tags.contains(&tg.id)); + .filter(|tg| state.tags.contains(&tg.id)); if let Some(first) = tags.next() { let mut layout = first.windows.as_master_stack(); @@ -302,7 +301,7 @@ impl XdgShellHandler for State { .tag_state .tags .iter_mut() - .filter(|tg| state.focused_tags.contains(&tg.id)); + .filter(|tg| state.tags.contains(&tg.id)); if let Some(first) = tags.next() { tracing::debug!("first tag: {:?}", first.id); @@ -429,10 +428,10 @@ impl XdgShellHandler for State { } fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { - tracing::debug!("start of ack_configure"); + // tracing::debug!("start of ack_configure"); if let Some(window) = self.window_for_surface(&surface) { - tracing::debug!("found window for surface"); - WindowState::with_state(&window, |state| { + // tracing::debug!("found window for surface"); + WindowState::with(&window, |state| { if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state { match &configure { Configure::Toplevel(configure) => { @@ -453,7 +452,7 @@ impl XdgShellHandler for State { // | mapping the element in commit, this means that the window won't reappear on a tag // | change. The code below is a workaround until I can figure it out. if !self.space.elements().any(|win| win == &window) { - WindowState::with_state(&window, |state| { + WindowState::with(&window, |state| { if let WindowResizeState::WaitingForCommit(new_loc) = state.resize_state { tracing::debug!("remapping window"); let win = window.clone(); diff --git a/src/layout.rs b/src/layout.rs index f978fd4..e638662 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -9,15 +9,11 @@ use smithay::{ desktop::{space::SpaceElement, Space, Window}, output::Output, utils::{Logical, Size}, + wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}, }; use crate::window::window_state::{WindowResizeState, WindowState}; -pub mod automatic; -pub mod manual; - -pub struct Layouts; - pub enum Direction { Left, Right, @@ -75,7 +71,7 @@ impl MasterStack<'_, Window> { state.size = Some((output_geo.size.w / 2, height).into()); }); - WindowState::with_state(win, |state| { + WindowState::with(win, |state| { state.resize_state = WindowResizeState::WaitingForAck( win.toplevel().send_configure(), (output_geo.size.w / 2, i as i32 * height).into(), @@ -101,12 +97,12 @@ pub fn swap_window_positions(space: &Space, win1: &Window, win2: &Window }); let serial = win1.toplevel().send_configure(); - WindowState::with_state(win1, |state| { + WindowState::with(win1, |state| { state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); }); let serial = win2.toplevel().send_configure(); - WindowState::with_state(win2, |state| { + WindowState::with(win2, |state| { state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); }); } @@ -127,7 +123,7 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { state.size = Some(output_geo.size); }); - WindowState::with_state(master, |state| { + WindowState::with(master, |state| { state.resize_state = WindowResizeState::WaitingForAck( master.toplevel().send_configure(), (0, 0).into(), @@ -143,7 +139,7 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { state.size = Some((output_geo.size.w / 2, output_geo.size.h).into()); }); - WindowState::with_state(master, |state| { + WindowState::with(master, |state| { state.resize_state = WindowResizeState::WaitingForAck( master.toplevel().send_configure(), (0, 0).into(), @@ -172,7 +168,7 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { state.size = Some(output_geo.size); }); - WindowState::with_state(master, |state| { + WindowState::with(master, |state| { state.resize_state = WindowResizeState::WaitingForAck( master.toplevel().send_configure(), (0, 0).into(), @@ -184,15 +180,46 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { } fn swap(&mut self, space: &Space, elem1: &Window, elem2: &Window) { - let mut elems = self.inner.iter_mut().flat_map(|vec| vec.iter_mut()); - let first = elems.find(|elem| *elem == elem1); - let second = elems.find(|elem| *elem == elem2); + tracing::debug!("top of swap"); + + let mut elems = self + .inner + .iter_mut() + .flat_map(|vec| vec.iter_mut()) + .filter(|elem| *elem == elem1 || *elem == elem2) + .unique_by(|win| WindowState::with(win, |state| state.id)); + + let (first, second) = (elems.next(), elems.next()); + if let Some(first) = first { if let Some(second) = second { std::mem::swap(first, second); } } + let wins = self + .inner + .iter() + .map(|vec| { + vec.iter() + .enumerate() + .map(|(i, win)| { + compositor::with_states(win.toplevel().wl_surface(), |states| { + let lock = states + .data_map + .get::() + .expect("XdgToplevelSurfaceData doesn't exist") + .lock() + .expect("Couldn't lock XdgToplevelSurfaceData"); + (i, lock.app_id.clone().unwrap_or("".to_string())) + }) + }) + .collect::>() + }) + .collect::>(); + + tracing::debug!("windows are: {wins:?}"); + swap_window_positions(space, elem1, elem2); } @@ -205,6 +232,27 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { tracing::error!("could not get output geometry"); return; }; + let wins = self + .inner + .iter() + .map(|vec| { + vec.iter() + .enumerate() + .map(|(i, win)| { + compositor::with_states(win.toplevel().wl_surface(), |states| { + let lock = states + .data_map + .get::() + .expect("XdgToplevelSurfaceData doesn't exist") + .lock() + .expect("Couldn't lock XdgToplevelSurfaceData"); + (i, lock.app_id.clone().unwrap_or("".to_string())) + }) + }) + .collect::>() + }) + .collect::>(); + tracing::debug!("windows are: {wins:?}"); if self.stack().count() == 0 { // one window @@ -212,7 +260,7 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { state.size = Some(output_geo.size); }); - WindowState::with_state(master, |state| { + WindowState::with(master, |state| { state.resize_state = WindowResizeState::WaitingForAck( master.toplevel().send_configure(), (0, 0).into(), @@ -224,7 +272,7 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { master.toplevel().with_pending_state(|state| { state.size = Some(new_master_size); }); - WindowState::with_state(master, |state| { + WindowState::with(master, |state| { state.resize_state = WindowResizeState::WaitingForAck( master.toplevel().send_configure(), (0, 0).into(), diff --git a/src/layout/automatic.rs b/src/layout/automatic.rs deleted file mode 100644 index 71d9179..0000000 --- a/src/layout/automatic.rs +++ /dev/null @@ -1,160 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// SPDX-License-Identifier: MPL-2.0 - -use smithay::{ - desktop::Window, - wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}, -}; - -use crate::{ - backend::Backend, - state::State, - window::window_state::{WindowResizeState, WindowState}, -}; - -use super::{Direction, Layouts}; - -impl Layouts { - pub fn master_stack( - state: &mut State, - mut windows: Vec, - side: Direction, - ) { - windows.retain(|win| WindowState::with_state(win, |state| state.floating.is_tiled())); - match side { - Direction::Left => { - let window_count = windows.len(); - if window_count == 0 { - return; - } - // TODO: change focused_output to be not an option - let Some(output) = state - .focus_state - .focused_output - .as_ref() - .or_else(|| state.space.outputs().next()) - else { - tracing::warn!("no connected outputs"); - return; - // TODO: no idea what happens if you spawn a window while no monitors are - // | connected, figure that out - }; - let output_size = state.space.output_geometry(output).unwrap().size; - if window_count == 1 { - tracing::debug!("Laying out only window"); - let window = windows[0].clone(); - - window.toplevel().with_pending_state(|tl_state| { - tl_state.size = Some(state.space.output_geometry(output).unwrap().size); - tracing::debug!("only size is {:?}", tl_state.size); - }); - - let initial_configure_sent = - compositor::with_states(window.toplevel().wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - tracing::debug!("initial configure sent is {initial_configure_sent}"); - if initial_configure_sent { - WindowState::with_state(&window, |state| { - tracing::debug!("sending configure"); - state.resize_state = WindowResizeState::WaitingForAck( - window.toplevel().send_configure(), - output.current_location(), - ); - }); - } - - return; - } - - tracing::debug!("layed out first window"); - let mut windows = windows.iter(); - let first_window = windows.next().unwrap(); - - first_window.toplevel().with_pending_state(|tl_state| { - let mut size = state.space.output_geometry(output).unwrap().size; - size.w /= 2; - tl_state.size = Some(size); - tracing::debug!("first size is {:?}", tl_state.size); - }); - - let initial_configure_sent = - compositor::with_states(first_window.toplevel().wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if initial_configure_sent { - WindowState::with_state(first_window, |state| { - tracing::debug!("sending resize state"); - state.resize_state = WindowResizeState::WaitingForAck( - first_window.toplevel().send_configure(), - output.current_location(), - ); - }); - } - - let window_count = windows.len() as i32; - let height = output_size.h / window_count; - let x = output.current_location().x + output_size.w / 2; - - for (i, win) in windows.enumerate() { - win.toplevel().with_pending_state(|state| { - let mut new_size = output_size; - new_size.w /= 2; - new_size.w = new_size.w.clamp(1, i32::MAX); - new_size.h /= window_count; - // INFO: The newest window won't have its geometry.loc set until after here and I don't know - // | why, so this is hardcoded to 40. I don't anticipate people using - // | windows that are that short, so figuring it out is low priority. - // | Kitty specifically will crash the compositor if it's resized such - // | that the bottom border goes above the bottom of the title bar if - // | this is set too low. - new_size.h = new_size.h.clamp(40, i32::MAX); - state.size = Some(new_size); - tracing::debug!("size is {:?}", state.size); - }); - - let mut new_loc = output.current_location(); - new_loc.x = x; - new_loc.y = (i as i32) * height; - - let initial_configure_sent = - compositor::with_states(win.toplevel().wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if initial_configure_sent { - WindowState::with_state(win, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - win.toplevel().send_configure(), - new_loc, - ); - }); - } - } - } - Direction::Right => todo!(), - Direction::Top => todo!(), - Direction::Bottom => todo!(), - } - } -} diff --git a/src/layout/manual.rs b/src/layout/manual.rs deleted file mode 100644 index 4690005..0000000 --- a/src/layout/manual.rs +++ /dev/null @@ -1,7 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// SPDX-License-Identifier: MPL-2.0 -// -// love how i'm licensing this empty file diff --git a/src/output.rs b/src/output.rs index 384f442..ccb3532 100644 --- a/src/output.rs +++ b/src/output.rs @@ -12,7 +12,7 @@ use crate::tag::TagId; #[derive(Default)] pub struct OutputState { - pub focused_tags: HashSet, + pub tags: HashSet, } impl OutputState { diff --git a/src/state.rs b/src/state.rs index ca09a02..94f4d08 100644 --- a/src/state.rs +++ b/src/state.rs @@ -136,7 +136,7 @@ impl State { Msg::SetWindowSize { window_id, size } => { let Some(window) = self.space.elements().find(|&win| { - WindowState::with_state(win, |state| state.id == window_id) + WindowState::with(win, |state| state.id == window_id) }) else { return; }; // TODO: tiled vs floating @@ -149,9 +149,9 @@ impl State { if let Some(window) = self .windows .iter() - .find(|&win| WindowState::with_state(win, |state| state.id == window_id)) + .find(|&win| WindowState::with(win, |state| state.id == window_id)) { - WindowState::with_state(window, |state| { + WindowState::with(window, |state| { state.tags = vec![tag_id.clone()]; }); } @@ -162,9 +162,9 @@ impl State { if let Some(window) = self .windows .iter() - .find(|&win| WindowState::with_state(win, |state| state.id == window_id)) + .find(|&win| WindowState::with(win, |state| state.id == window_id)) { - WindowState::with_state(window, |state| { + WindowState::with(window, |state| { if state.tags.contains(&tag_id) { state.tags.retain(|id| id != &tag_id); } else { @@ -179,12 +179,12 @@ impl State { OutputState::with( self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error |state| { - let should_remove = state.focused_tags.get(&tag_id).is_some(); + let should_remove = state.tags.get(&tag_id).is_some(); if should_remove { - state.focused_tags.remove(&tag_id); + state.tags.remove(&tag_id); tracing::debug!("toggled tag {tag_id:?} off"); } else { - state.focused_tags.insert(tag_id.clone()); + state.tags.insert(tag_id.clone()); tracing::debug!("toggled tag {tag_id:?} on"); } }, @@ -194,9 +194,9 @@ impl State { } Msg::SwitchToTag { tag_id } => { OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - state.focused_tags.clear(); - state.focused_tags.insert(tag_id.clone()); - tracing::debug!("focused tags: {:?}", state.focused_tags); + state.tags.clear(); + state.tags.insert(tag_id.clone()); + tracing::debug!("focused tags: {:?}", state.tags); }); self.re_layout(); @@ -238,7 +238,7 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = WindowState::with_state(¤t_focus, |state| { + let (window_id, floating) = WindowState::with(¤t_focus, |state| { (state.id, state.floating.is_floating()) }); // TODO: unwrap @@ -281,7 +281,7 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = WindowState::with_state(win, |state| { + let (window_id, floating) = WindowState::with(win, |state| { (state.id, state.floating.is_floating()) }); // TODO: unwrap @@ -471,46 +471,38 @@ impl State { } pub fn re_layout(&mut self) { - let mut windows = - OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - for window in self.space.elements().cloned().collect::>() { - let should_render = WindowState::with_state(&window, |win_state| { - for tag_id in win_state.tags.iter() { - if state.focused_tags.get(tag_id).is_some() { - return true; - } + let output = self.focus_state.focused_output.as_ref().unwrap(); + OutputState::with(output, |state| { + for window in self.space.elements().cloned().collect::>() { + let should_render = WindowState::with(&window, |win_state| { + for tag_id in win_state.tags.iter() { + if state.tags.get(tag_id).is_some() { + return true; } - false - }); - if !should_render { - self.space.unmap_elem(&window); } + false + }); + if !should_render { + self.space.unmap_elem(&window); + } + } + + let mut tags = self + .tag_state + .tags + .iter_mut() + .filter(|tg| state.tags.contains(&tg.id)); + + if let Some(first) = tags.next() { + let mut layout = first.windows.as_master_stack(); + + for tg in tags { + layout = layout.chain_with(&mut tg.windows); } - self.windows - .iter() - .filter(|&win| { - WindowState::with_state(win, |win_state| { - for tag_id in win_state.tags.iter() { - if state.focused_tags.get(tag_id).is_some() { - return true; - } - } - false - }) - }) - .cloned() - .collect::>() - }); - - tracing::debug!("Laying out {} windows", windows.len()); - - windows.as_master_stack().layout( - &self.space, - self.focus_state.focused_output.as_ref().unwrap(), - ); - - // Layouts::master_stack(self, windows, crate::layout::Direction::Left); + layout.layout(&self.space, output); + } + }); } } diff --git a/src/window.rs b/src/window.rs index 1c58b23..380abac 100644 --- a/src/window.rs +++ b/src/window.rs @@ -12,9 +12,7 @@ use smithay::{ wayland::{compositor, seat::WaylandFocus}, }; -use crate::{ - backend::Backend, layout::Layouts, state::State, window::window_state::WindowResizeState, -}; +use crate::{backend::Backend, state::State}; use self::window_state::{Float, WindowId, WindowState}; @@ -57,43 +55,11 @@ impl State { .cloned() }) } - - /// Swap the positions and sizes of two windows. - pub fn swap_window_positions(&mut self, win1: &Window, win2: &Window) { - // FIXME: moving the mouse quickly will break swapping - - let win1_loc = self.space.element_location(win1).unwrap(); // TODO: handle unwraps - let win2_loc = self.space.element_location(win2).unwrap(); - let win1_geo = win1.geometry(); - let win2_geo = win2.geometry(); - // tracing::info!("win1: {:?}, {:?}", win1_loc, win1_geo); - // tracing::info!("win2: {:?}, {:?}", win2_loc, win2_geo); - - win1.toplevel().with_pending_state(|state| { - state.size = Some(win2_geo.size); - }); - win2.toplevel().with_pending_state(|state| { - state.size = Some(win1_geo.size); - }); - - let serial = win1.toplevel().send_configure(); - WindowState::with_state(win1, |state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); - }); - - let serial = win2.toplevel().send_configure(); - WindowState::with_state(win2, |state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); - }); - - // self.space.map_element(win1.clone(), win2_loc, false); - // self.space.map_element(win2.clone(), win1_loc, false); - } } /// Toggle a window's floating status. pub fn toggle_floating(state: &mut State, window: &Window) { - WindowState::with_state(window, |window_state| { + WindowState::with(window, |window_state| { match window_state.floating { Float::Tiled(prev_loc_and_size) => { if let Some((prev_loc, prev_size)) = prev_loc_and_size { @@ -119,8 +85,7 @@ pub fn toggle_floating(state: &mut State, window: &Window) { } }); - let windows = state.space.elements().cloned().collect::>(); - Layouts::master_stack(state, windows, crate::layout::Direction::Left); + state.re_layout(); state.space.raise_element(window, true); } diff --git a/src/window/window_state.rs b/src/window/window_state.rs index c8f1731..9ddc930 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -16,7 +16,7 @@ use smithay::{ use crate::tag::{Tag, TagId, TagState}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct WindowId(u32); // TODO: this probably doesn't need to be atomic @@ -44,7 +44,7 @@ pub fn tags<'a>(tag_state: &'a TagState, window: &Window) -> Vec<&'a Tag> { tag_state .tags .iter() - .filter(|&tag| WindowState::with_state(window, |state| state.tags.contains(&tag.id))) + .filter(|&tag| WindowState::with(window, |state| state.tags.contains(&tag.id))) .collect() } @@ -118,7 +118,7 @@ impl WindowState { } /// Access a [Window]'s state, optionally returning something. - pub fn with_state(window: &Window, mut func: F) -> T + pub fn with(window: &Window, mut func: F) -> T where F: FnMut(&mut Self) -> T, { From c85ada88e8cde3ea897d5703af79fc3d39e02310 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sun, 9 Jul 2023 14:02:21 -0500 Subject: [PATCH 07/27] Update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c7ace4..a8e4975 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Cool stuff happens on the dev branch sometimes, check it out! - [ ] Server-side decorations - [ ] The other stuff Awesome has - [ ] XWayland support +- [ ] Layer-shell support - [x] Is very cool :thumbsup: ## Info @@ -110,5 +111,6 @@ The following controls are currently hardcoded: - `Ctrl + Left Mouse`: Move a window - `Ctrl + Right Mouse`: Resize a window +- `Ctrl + Alt + Shift + Esc`: Kill Pinnacle. This is for when the compositor inevitably locks up because I did a dumb thing :thumbsup: You can find the rest of the controls in the [`example_config`](api/lua/example_config.lua). From ba69b21c5256856a3ee3e367da3518e7413f01c9 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sun, 9 Jul 2023 17:48:46 -0500 Subject: [PATCH 08/27] Rework window tracking --- src/api/msg.rs | 18 ++- src/grab/move_grab.rs | 22 +--- src/handlers.rs | 102 ++++++---------- src/layout.rs | 238 +++++++++---------------------------- src/output.rs | 16 ++- src/state.rs | 108 ++++++++++------- src/tag.rs | 40 +++++-- src/window.rs | 9 +- src/window/window_state.rs | 17 +-- 9 files changed, 227 insertions(+), 343 deletions(-) diff --git a/src/api/msg.rs b/src/api/msg.rs index 97cb8bf..5fca1f2 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -7,10 +7,7 @@ // The MessagePack format for these is a one-element map where the element's key is the enum name and its // value is a map of the enum's values -use crate::{ - tag::TagId, - window::{window_state::WindowId, WindowProperties}, -}; +use crate::window::{window_state::WindowId, WindowProperties}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); @@ -42,25 +39,26 @@ pub enum Msg { }, MoveWindowToTag { window_id: WindowId, - tag_id: TagId, + tag_id: String, }, ToggleTagOnWindow { window_id: WindowId, - tag_id: TagId, + tag_id: String, }, // Tag management ToggleTag { - tag_id: TagId, + tag_id: String, }, SwitchToTag { - tag_id: TagId, + tag_id: String, }, AddTags { - tags: Vec, + tags: Vec, }, RemoveTags { - tags: Vec, + // TODO: + tags: Vec, }, // Process management diff --git a/src/grab/move_grab.rs b/src/grab/move_grab.rs index 4df0b37..d37a2e9 100644 --- a/src/grab/move_grab.rs +++ b/src/grab/move_grab.rs @@ -21,8 +21,6 @@ use smithay::{ use crate::{ backend::Backend, - layout::{Layout, LayoutVec}, - output::OutputState, state::State, window::window_state::{WindowResizeState, WindowState}, }; @@ -93,25 +91,7 @@ impl PointerGrab> for MoveSurfaceGrab> { return; } - // data.swap_window_positions(&self.window, &window_under); - let output = data.focus_state.focused_output.as_ref().unwrap(); - OutputState::with(output, |state| { - let mut tags = data - .tag_state - .tags - .iter_mut() - .filter(|tg| state.tags.contains(&tg.id)); - - if let Some(first) = tags.next() { - let mut layout = first.windows.as_master_stack(); - - for tg in tags { - layout = layout.chain_with(&mut tg.windows); - } - - layout.swap(&data.space, &self.window, &window_under); - } - }) + data.swap_window_positions(&self.window, &window_under); } } else { let delta = event.location - self.start_data.location; diff --git a/src/handlers.rs b/src/handlers.rs index faf7b90..1d03583 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -50,7 +50,7 @@ use crate::{ layout::{Layout, LayoutVec}, output::OutputState, state::{ClientState, State}, - window::window_state::{WindowResizeState, WindowState}, + window::window_state::{CommitState, WindowResizeState, WindowState}, }; impl BufferHandler for State { @@ -122,6 +122,17 @@ impl CompositorHandler for State { state.resize_state = WindowResizeState::Idle; self.space.map_element(window.clone(), new_pos, false); } + + if let CommitState::Acked = state.needs_raise { + let clone = window.clone(); + + // FIXME: happens before the other windows ack, so it's useless + self.loop_handle.insert_idle(move |data| { + tracing::debug!("raising window"); + data.state.space.raise_element(&clone, true); + }); + state.needs_raise = CommitState::Idle; + } }); } } @@ -226,28 +237,31 @@ impl XdgShellHandler for State { let window = Window::new(surface); WindowState::with(&window, |state| { - state.tags = if let Some(focused_output) = &self.focus_state.focused_output { - OutputState::with(focused_output, |state| { - let output_tags: Vec = state.tags.iter().cloned().collect(); + state.tags = match ( + &self.focus_state.focused_output, + self.space.outputs().next(), + ) { + (Some(output), _) | (None, Some(output)) => OutputState::with(output, |state| { + let output_tags = state + .focused_tags() + .map(|tag| tag.id.clone()) + .collect::>(); if !output_tags.is_empty() { output_tags - } else if let Some(first_tag) = self.tag_state.tags.first() { + } else if let Some(first_tag) = state.tags.first() { vec![first_tag.id.clone()] } else { vec![] } - }) // TODO: repetition - } else if let Some(first_tag) = self.tag_state.tags.first() { - vec![first_tag.id.clone()] - } else { - vec![] + }), + (None, None) => vec![], }; + tracing::debug!("new window, tags are {:?}", state.tags); }); self.windows.push(window.clone()); // self.space.map_element(window.clone(), (0, 0), true); - let clone = window.clone(); self.loop_handle.insert_idle(move |data| { data.state .seat @@ -255,7 +269,7 @@ impl XdgShellHandler for State { .expect("Seat had no keyboard") // FIXME: actually handle error .set_focus( &mut data.state, - Some(clone.toplevel().wl_surface().clone()), + Some(window.toplevel().wl_surface().clone()), SERIAL_COUNTER.next_serial(), ); }); @@ -263,26 +277,10 @@ impl XdgShellHandler for State { self.loop_handle.insert_idle(move |data| { if let Some(focused_output) = &data.state.focus_state.focused_output { OutputState::with(focused_output, |state| { - let window = window.clone(); - let mut tags = data - .state - .tag_state - .tags - .iter_mut() - .filter(|tg| state.tags.contains(&tg.id)); - - if let Some(first) = tags.next() { - let mut layout = first.windows.as_master_stack(); - - for tg in tags { - layout = layout.chain_with(&mut tg.windows); - } - - layout.add(&data.state.space, focused_output, window); - } - for tag in data.state.tag_state.tags.iter() { - tracing::debug!("tag {:?}, {}", tag.id, tag.windows.len()); - } + data.state + .windows + .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) + .layout(&data.state.space, focused_output); }); } }); @@ -290,44 +288,15 @@ impl XdgShellHandler for State { fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { tracing::debug!("toplevel destroyed"); - let window = self - .windows - .iter() - .find(|&win| win.toplevel() == &surface) - .unwrap(); + self.windows.retain(|window| window.toplevel() != &surface); if let Some(focused_output) = self.focus_state.focused_output.as_ref() { OutputState::with(focused_output, |state| { - let mut tags = self - .tag_state - .tags - .iter_mut() - .filter(|tg| state.tags.contains(&tg.id)); - - if let Some(first) = tags.next() { - tracing::debug!("first tag: {:?}", first.id); - let mut layout = first.windows.as_master_stack(); - - for tg in tags { - tracing::debug!("tag: {:?}", tg.id); - layout = layout.chain_with(&mut tg.windows); - } - - // This will only remove the window from focused tags... - layout.remove(&self.space, focused_output, window); - } - - // ...so here we remove the window from any tag that isn't focused - for tag in self.tag_state.tags.iter_mut() { - tag.windows.retain(|el| el != window); - } + self.windows + .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) + .layout(&self.space, focused_output); }); } - for tag in self.tag_state.tags.iter() { - tracing::debug!("tag {:?}, {}", tag.id, tag.windows.len()); - } - - self.windows.retain(|window| window.toplevel() != &surface); // let mut windows: Vec = self.space.elements().cloned().collect(); // windows.retain(|window| window.toplevel() != &surface); // Layouts::master_stack(self, windows, crate::layout::Direction::Left); @@ -443,6 +412,9 @@ impl XdgShellHandler for State { Configure::Popup(_) => todo!(), } } + if let CommitState::RequestReceived(_serial) = state.needs_raise { + state.needs_raise = CommitState::Acked; + } }); // HACK: If a window is currently going through something that generates a bunch of diff --git a/src/layout.rs b/src/layout.rs index e638662..6158bf4 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,15 +4,18 @@ // // SPDX-License-Identifier: MPL-2.0 -use itertools::Itertools; use smithay::{ desktop::{space::SpaceElement, Space, Window}, output::Output, utils::{Logical, Size}, - wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}, }; -use crate::window::window_state::{WindowResizeState, WindowState}; +use crate::{ + backend::Backend, + state::State, + tag::TagId, + window::window_state::{WindowResizeState, WindowState}, +}; pub enum Direction { Left, @@ -21,41 +24,26 @@ pub enum Direction { Bottom, } -pub struct MasterStack<'a, S: SpaceElement> { - inner: Vec<&'a mut Vec>, +pub struct MasterStack { + inner: Vec, } pub trait Layout<'a, S: SpaceElement> { - /// Add a [`SpaceElement`] to this layout and update positions. - fn add(&mut self, space: &Space, output: &Output, elem: S); - /// Remove a [`SpaceElement`] from this layout and update positions. - fn remove(&mut self, space: &Space, output: &Output, elem: &S); - - // TODO: return result - /// Swap two elements in this layout and update their positions. - fn swap(&mut self, space: &Space, elem1: &S, elem2: &S); - /// Perform a full layout with all elements. Use this when you are switching from another layout. fn layout(&self, space: &Space, output: &Output); - - fn chain_with(self, vec: &'a mut Vec) -> Self; } -impl MasterStack<'_, Window> { +impl MasterStack { pub fn master(&self) -> Option<&Window> { - self.inner.iter().flat_map(|vec| vec.iter()).next() + self.inner.first() } pub fn stack(&self) -> impl Iterator { - self.inner - .iter() - .flat_map(|vec| vec.iter()) - .unique() - .skip(1) + self.inner.iter().skip(1) } } -impl MasterStack<'_, Window> { +impl MasterStack { fn layout_stack(&self, space: &Space, output: &Output) { let stack_count = self.stack().count(); @@ -81,113 +69,36 @@ impl MasterStack<'_, Window> { } } -pub fn swap_window_positions(space: &Space, win1: &Window, win2: &Window) { - // FIXME: moving the mouse quickly will break swapping +impl State { + pub fn swap_window_positions(&mut self, win1: &Window, win2: &Window) { + // FIXME: moving the mouse quickly will break swapping - let win1_loc = space.element_location(win1).unwrap(); // TODO: handle unwraps - let win2_loc = space.element_location(win2).unwrap(); - let win1_geo = win1.geometry(); - let win2_geo = win2.geometry(); + let win1_loc = self.space.element_location(win1).unwrap(); // TODO: handle unwraps + let win2_loc = self.space.element_location(win2).unwrap(); + let win1_geo = win1.geometry(); + let win2_geo = win2.geometry(); - win1.toplevel().with_pending_state(|state| { - state.size = Some(win2_geo.size); - }); - win2.toplevel().with_pending_state(|state| { - state.size = Some(win1_geo.size); - }); + win1.toplevel().with_pending_state(|state| { + state.size = Some(win2_geo.size); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some(win1_geo.size); + }); - let serial = win1.toplevel().send_configure(); - WindowState::with(win1, |state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); - }); + let serial = win1.toplevel().send_configure(); + WindowState::with(win1, |state| { + state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); + }); - let serial = win2.toplevel().send_configure(); - WindowState::with(win2, |state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); - }); -} - -impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { - fn add(&mut self, space: &Space, output: &Output, elem: Window) { - for vec in self.inner.iter_mut() { - vec.push(elem.clone()); - } - - if self.stack().count() == 0 { - let Some(master) = self.master() else { unreachable!() }; - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - master.toplevel().with_pending_state(|state| { - state.size = Some(output_geo.size); - }); - - WindowState::with(master, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (0, 0).into(), - ); - }); - } else if self.stack().count() == 1 { - let Some(master) = self.master() else { unreachable!() }; - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - master.toplevel().with_pending_state(|state| { - state.size = Some((output_geo.size.w / 2, output_geo.size.h).into()); - }); - - WindowState::with(master, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (0, 0).into(), - ); - }); - self.layout_stack(space, output); - } else { - self.layout_stack(space, output); - } - } - - fn remove(&mut self, space: &Space, output: &Output, elem: &Window) { - for vec in self.inner.iter_mut() { - vec.retain(|el| el != elem); - } - - let Some(master) = self.master() else { return }; - - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - if self.stack().count() == 0 { - master.toplevel().with_pending_state(|state| { - state.size = Some(output_geo.size); - }); - - WindowState::with(master, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (0, 0).into(), - ); - }); - } else { - self.layout_stack(space, output); - } - } - - fn swap(&mut self, space: &Space, elem1: &Window, elem2: &Window) { - tracing::debug!("top of swap"); + let serial = win2.toplevel().send_configure(); + WindowState::with(win2, |state| { + state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); + }); let mut elems = self - .inner + .windows .iter_mut() - .flat_map(|vec| vec.iter_mut()) - .filter(|elem| *elem == elem1 || *elem == elem2) - .unique_by(|win| WindowState::with(win, |state| state.id)); + .filter(|win| *win == win1 || *win == win2); let (first, second) = (elems.next(), elems.next()); @@ -196,33 +107,10 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { std::mem::swap(first, second); } } - - let wins = self - .inner - .iter() - .map(|vec| { - vec.iter() - .enumerate() - .map(|(i, win)| { - compositor::with_states(win.toplevel().wl_surface(), |states| { - let lock = states - .data_map - .get::() - .expect("XdgToplevelSurfaceData doesn't exist") - .lock() - .expect("Couldn't lock XdgToplevelSurfaceData"); - (i, lock.app_id.clone().unwrap_or("".to_string())) - }) - }) - .collect::>() - }) - .collect::>(); - - tracing::debug!("windows are: {wins:?}"); - - swap_window_positions(space, elem1, elem2); } +} +impl<'a> Layout<'a, Window> for MasterStack { fn layout(&self, space: &Space, output: &Output) { let Some(master) = self.master() else { return; @@ -232,27 +120,6 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { tracing::error!("could not get output geometry"); return; }; - let wins = self - .inner - .iter() - .map(|vec| { - vec.iter() - .enumerate() - .map(|(i, win)| { - compositor::with_states(win.toplevel().wl_surface(), |states| { - let lock = states - .data_map - .get::() - .expect("XdgToplevelSurfaceData doesn't exist") - .lock() - .expect("Couldn't lock XdgToplevelSurfaceData"); - (i, lock.app_id.clone().unwrap_or("".to_string())) - }) - }) - .collect::>() - }) - .collect::>(); - tracing::debug!("windows are: {wins:?}"); if self.stack().count() == 0 { // one window @@ -282,22 +149,33 @@ impl<'a> Layout<'a, Window> for MasterStack<'a, Window> { self.layout_stack(space, output); } } - - /// Chain another tag's windows to this one to be layed out. - fn chain_with(mut self, vec: &'a mut Vec) -> Self { - self.inner.push(vec); - self - } } pub trait LayoutVec { /// Interpret this vec as a master-stack layout. - fn as_master_stack(&mut self) -> MasterStack; + fn to_master_stack(&self, tags: Vec) -> MasterStack; // fn as_binary_tree(&mut self); TODO: } -impl LayoutVec for Vec { - fn as_master_stack(&mut self) -> MasterStack { - MasterStack { inner: vec![self] } +impl LayoutVec for Vec { + fn to_master_stack(&self, tags: Vec) -> MasterStack { + MasterStack { + inner: self + .iter() + .filter(|window| { + WindowState::with(window, |state| { + state.floating.is_tiled() && { + for tag_id in state.tags.iter() { + if tags.iter().any(|tag| tag == tag_id) { + return true; + } + } + false + } + }) + }) + .cloned() + .collect(), + } } } diff --git a/src/output.rs b/src/output.rs index ccb3532..71884b6 100644 --- a/src/output.rs +++ b/src/output.rs @@ -4,15 +4,21 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::{cell::RefCell, collections::HashSet}; +use std::cell::RefCell; use smithay::output::Output; -use crate::tag::TagId; +use crate::tag::Tag; #[derive(Default)] pub struct OutputState { - pub tags: HashSet, + pub tags: Vec, +} + +impl OutputState { + pub fn focused_tags(&mut self) -> impl Iterator { + self.tags.iter_mut().filter(|tag| tag.active) + } } impl OutputState { @@ -24,10 +30,10 @@ impl OutputState { .user_data() .insert_if_missing(RefCell::::default); - let state = output + let mut state = output .user_data() .get::>() - .expect("RefCell doesn't exist in data map (This should NEVER happen. If you see this, something oofed big-time.)"); + .expect("RefCell not in data map"); func(&mut state.borrow_mut()) } diff --git a/src/state.rs b/src/state.rs index 94f4d08..a688256 100644 --- a/src/state.rs +++ b/src/state.rs @@ -21,7 +21,7 @@ use crate::{ focus::FocusState, layout::{Layout, LayoutVec}, output::OutputState, - tag::{Tag, TagState}, + tag::Tag, window::{window_state::WindowState, WindowProperties}, }; use calloop::futures::Scheduler; @@ -88,7 +88,6 @@ pub struct State { pub input_state: InputState, pub api_state: ApiState, pub focus_state: FocusState, - pub tag_state: TagState, pub popup_manager: PopupManager, @@ -152,7 +151,15 @@ impl State { .find(|&win| WindowState::with(win, |state| state.id == window_id)) { WindowState::with(window, |state| { - state.tags = vec![tag_id.clone()]; + OutputState::with( + &self.focus_state.focused_output.as_ref().unwrap(), + |op_state| { + let tag = op_state.tags.iter().find(|tag| tag.name == tag_id); + if let Some(tag) = tag { + state.tags = vec![tag.id.clone()]; + } + }, + ); }); } @@ -165,11 +172,19 @@ impl State { .find(|&win| WindowState::with(win, |state| state.id == window_id)) { WindowState::with(window, |state| { - if state.tags.contains(&tag_id) { - state.tags.retain(|id| id != &tag_id); - } else { - state.tags.push(tag_id.clone()); - } + OutputState::with( + &self.focus_state.focused_output.as_ref().unwrap(), + |op_state| { + 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); + } else { + state.tags.push(tag.id.clone()); + } + } + }, + ); }); self.re_layout(); @@ -179,13 +194,8 @@ impl State { OutputState::with( self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error |state| { - let should_remove = state.tags.get(&tag_id).is_some(); - if should_remove { - state.tags.remove(&tag_id); - tracing::debug!("toggled tag {tag_id:?} off"); - } else { - state.tags.insert(tag_id.clone()); - tracing::debug!("toggled tag {tag_id:?} on"); + if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) { + tag.active = !tag.active; } }, ); @@ -194,13 +204,33 @@ impl State { } Msg::SwitchToTag { tag_id } => { OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - state.tags.clear(); - state.tags.insert(tag_id.clone()); - tracing::debug!("focused tags: {:?}", state.tags); + if !state.tags.iter().any(|tag| tag.name == tag_id) { + // TODO: notify error + return; + } + for tag in state.tags.iter_mut() { + tag.active = false; + } + + let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) else { + unreachable!() + }; + tag.active = true; + + tracing::debug!( + "focused tags: {:?}", + state + .tags + .iter() + .filter(|tag| tag.active) + .map(|tag| &tag.name) + .collect::>() + ); }); self.re_layout(); } + // TODO: add output Msg::AddTags { tags } => { if let Some(output) = self .focus_state @@ -208,15 +238,21 @@ impl State { .as_ref() .or_else(|| self.space.outputs().next()) { - self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag { - id: tag, - windows: vec![], - output: output.clone(), - })); + OutputState::with(output, |state| { + state.tags.extend( + tags.clone() + .into_iter() + .map(|name| Tag::new(name, output.clone())), + ); + }); } } Msg::RemoveTags { tags } => { - self.tag_state.tags.retain(|tag| !tags.contains(&tag.id)); + for output in self.space.outputs() { + OutputState::with(output, |state| { + state.tags.retain(|tag| !tags.contains(&tag.name)); + }); + } } Msg::Quit => { @@ -475,8 +511,11 @@ impl State { OutputState::with(output, |state| { for window in self.space.elements().cloned().collect::>() { let should_render = WindowState::with(&window, |win_state| { + if win_state.floating.is_floating() { + return true; + } for tag_id in win_state.tags.iter() { - if state.tags.get(tag_id).is_some() { + if state.focused_tags().any(|tag| &tag.id == tag_id) { return true; } } @@ -487,21 +526,9 @@ impl State { } } - let mut tags = self - .tag_state - .tags - .iter_mut() - .filter(|tg| state.tags.contains(&tg.id)); - - if let Some(first) = tags.next() { - let mut layout = first.windows.as_master_stack(); - - for tg in tags { - layout = layout.chain_with(&mut tg.windows); - } - - layout.layout(&self.space, output); - } + self.windows + .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) + .layout(&self.space, output); }); } } @@ -644,7 +671,6 @@ impl State { input_state: InputState::new(), api_state: ApiState::new(), focus_state: FocusState::new(), - tag_state: TagState::new(), seat, diff --git a/src/tag.rs b/src/tag.rs index b9b311c..63955c5 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -4,26 +4,44 @@ // // SPDX-License-Identifier: MPL-2.0 -use smithay::{desktop::Window, output::Output}; +use std::{ + hash::Hash, + sync::atomic::{AtomicU32, Ordering}, +}; + +use smithay::output::Output; + +static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0); #[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] -pub struct TagId(String); +pub struct TagId(u32); + +impl TagId { + fn next() -> Self { + Self(TAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed)) + } +} #[derive(Debug)] pub struct Tag { + /// The internal id of this tag. pub id: TagId, - pub windows: Vec, + /// The name of this tag. + pub name: String, + /// The output that this tag should be on. pub output: Output, + /// Whether this tag is active or not. + pub active: bool, // TODO: layout } -#[derive(Debug, Default)] -pub struct TagState { - pub tags: Vec, -} - -impl TagState { - pub fn new() -> Self { - Default::default() +impl Tag { + pub fn new(name: String, output: Output) -> Self { + Self { + id: TagId::next(), + name, + output, + active: false, + } } } diff --git a/src/window.rs b/src/window.rs index 380abac..ab98fd4 100644 --- a/src/window.rs +++ b/src/window.rs @@ -14,7 +14,7 @@ use smithay::{ use crate::{backend::Backend, state::State}; -use self::window_state::{Float, WindowId, WindowState}; +use self::window_state::{CommitState, Float, WindowId, WindowState}; pub mod window_state; @@ -86,7 +86,12 @@ pub fn toggle_floating(state: &mut State, window: &Window) { }); state.re_layout(); - state.space.raise_element(window, true); + + // FIXME: every window gets mapped after this raise in `commit`, making this useless + WindowState::with(window, |state| { + state.needs_raise = CommitState::RequestReceived(window.toplevel().send_configure()); + }); + // state.space.raise_element(window, true); } #[derive(Debug, serde::Serialize, serde::Deserialize)] diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 9ddc930..b40e6a9 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -14,7 +14,7 @@ use smithay::{ utils::{Logical, Point, Serial, Size}, }; -use crate::tag::{Tag, TagId, TagState}; +use crate::tag::TagId; #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct WindowId(u32); @@ -37,15 +37,15 @@ pub struct WindowState { pub resize_state: WindowResizeState, /// What tags the window is currently on. pub tags: Vec, + + // FIXME: this is a bandaid to get floating working again, figure out an actual solution + pub needs_raise: CommitState, } -/// Returns a vec of references to all the tags the window is on. -pub fn tags<'a>(tag_state: &'a TagState, window: &Window) -> Vec<&'a Tag> { - tag_state - .tags - .iter() - .filter(|&tag| WindowState::with(window, |state| state.tags.contains(&tag.id))) - .collect() +pub enum CommitState { + Idle, + RequestReceived(Serial), + Acked, } /// The state of a window's resize operation. @@ -142,6 +142,7 @@ impl Default for WindowState { floating: Float::Tiled(None), resize_state: WindowResizeState::Idle, tags: vec![], + needs_raise: CommitState::Idle, } } } From 7dac7ba8cdd990777ad603b67250f517e7311fe5 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sun, 9 Jul 2023 20:33:25 -0500 Subject: [PATCH 09/27] Fix windows flickering on tag change --- src/layout.rs | 1 - src/state.rs | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 6158bf4..aadee71 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -29,7 +29,6 @@ pub struct MasterStack { } pub trait Layout<'a, S: SpaceElement> { - /// Perform a full layout with all elements. Use this when you are switching from another layout. fn layout(&self, space: &Space, output: &Output); } diff --git a/src/state.rs b/src/state.rs index a688256..51f5216 100644 --- a/src/state.rs +++ b/src/state.rs @@ -22,7 +22,10 @@ use crate::{ layout::{Layout, LayoutVec}, output::OutputState, tag::Tag, - window::{window_state::WindowState, WindowProperties}, + window::{ + window_state::{WindowResizeState, WindowState}, + WindowProperties, + }, }; use calloop::futures::Scheduler; use futures_lite::AsyncBufReadExt; @@ -508,9 +511,12 @@ impl State { pub fn re_layout(&mut self) { let output = self.focus_state.focused_output.as_ref().unwrap(); - OutputState::with(output, |state| { - for window in self.space.elements().cloned().collect::>() { - let should_render = WindowState::with(&window, |win_state| { + let (render, do_not_render) = OutputState::with(output, |state| { + self.windows + .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) + .layout(&self.space, output); + self.windows.iter().cloned().partition::, _>(|win| { + WindowState::with(win, |win_state| { if win_state.floating.is_floating() { return true; } @@ -520,15 +526,42 @@ impl State { } } false + }) + }) + }); + self.schedule_on_commit(render, |data| { + for win in do_not_render { + data.state.space.unmap_elem(&win); + } + }); + } + + /// Schedule something to be done when windows have finished committing and have become + /// idle. + pub fn schedule_on_commit(&mut self, windows: Vec, on_commit: F) + where + F: FnOnce(&mut CalloopData) + 'static, + { + tracing::debug!("scheduling on_commit"); + self.loop_handle.insert_idle(|data| { + tracing::debug!("running idle cb"); + tracing::debug!("win len is {}", windows.len()); + for window in windows.iter() { + WindowState::with(window, |state| { + tracing::debug!("win state is {:?}", state.resize_state); }); - if !should_render { - self.space.unmap_elem(&window); + if WindowState::with(window, |state| { + !matches!(state.resize_state, WindowResizeState::Idle) + }) { + tracing::debug!("some windows not idle"); + data.state.loop_handle.insert_idle(|data| { + data.state.schedule_on_commit(windows, on_commit); + }); + return; } } - self.windows - .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) - .layout(&self.space, output); + on_commit(data); }); } } From ac6130408b75f406f87cbd921f085c56e7cacc63 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Sun, 9 Jul 2023 21:04:26 -0500 Subject: [PATCH 10/27] Fix window not raising when toggled to floating --- src/window.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/window.rs b/src/window.rs index ab98fd4..b3c2ffd 100644 --- a/src/window.rs +++ b/src/window.rs @@ -12,9 +12,9 @@ use smithay::{ wayland::{compositor, seat::WaylandFocus}, }; -use crate::{backend::Backend, state::State}; +use crate::{backend::Backend, output::OutputState, state::State}; -use self::window_state::{CommitState, Float, WindowId, WindowState}; +use self::window_state::{Float, WindowId, WindowState}; pub mod window_state; @@ -87,11 +87,32 @@ pub fn toggle_floating(state: &mut State, window: &Window) { state.re_layout(); - // FIXME: every window gets mapped after this raise in `commit`, making this useless - WindowState::with(window, |state| { - state.needs_raise = CommitState::RequestReceived(window.toplevel().send_configure()); + let output = state.focus_state.focused_output.as_ref().unwrap(); + let render = OutputState::with(output, |op_state| { + state + .windows + .iter() + .cloned() + .filter(|win| { + WindowState::with(win, |win_state| { + 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) { + return true; + } + } + false + }) + }) + .collect::>() + }); + + let clone = window.clone(); + state.schedule_on_commit(render, move |data| { + data.state.space.raise_element(&clone, true); }); - // state.space.raise_element(window, true); } #[derive(Debug, serde::Serialize, serde::Deserialize)] From 930925a8f1b16deda260298c9c562de2a67dd0ae Mon Sep 17 00:00:00 2001 From: Ottatop Date: Mon, 10 Jul 2023 17:14:37 -0500 Subject: [PATCH 11/27] Change all *State.with(thing, ...) to thing.with_state(...) --- src/grab/move_grab.rs | 14 ++- src/grab/resize_grab.rs | 20 +++-- src/handlers.rs | 33 ++----- src/layout.rs | 180 ++++++++++++++++++++----------------- src/output.rs | 39 ++++---- src/state.rs | 154 +++++++++++++++++++------------ src/window.rs | 43 +++------ src/window/window_state.rs | 33 +++---- 8 files changed, 263 insertions(+), 253 deletions(-) diff --git a/src/grab/move_grab.rs b/src/grab/move_grab.rs index d37a2e9..bf116a4 100644 --- a/src/grab/move_grab.rs +++ b/src/grab/move_grab.rs @@ -21,8 +21,8 @@ use smithay::{ use crate::{ backend::Backend, - state::State, - window::window_state::{WindowResizeState, WindowState}, + state::{State, WithState}, + window::window_state::WindowResizeState, }; pub struct MoveSurfaceGrab { @@ -51,7 +51,7 @@ impl PointerGrab> for MoveSurfaceGrab> { // tracing::info!("window geo is: {:?}", self.window.geometry()); // tracing::info!("loc is: {:?}", data.space.element_location(&self.window)); - let tiled = WindowState::with(&self.window, |state| state.floating.is_tiled()); + let tiled = self.window.with_state(|state| state.floating.is_tiled()); if tiled { // INFO: this is being used instead of space.element_under(event.location) because that @@ -76,16 +76,14 @@ impl PointerGrab> for MoveSurfaceGrab> { return; } - let is_floating = - WindowState::with(&window_under, |state| state.floating.is_floating()); + let is_floating = window_under.with_state(|state| state.floating.is_floating()); if is_floating { return; } - let has_pending_resize = WindowState::with(&window_under, |state| { - !matches!(state.resize_state, WindowResizeState::Idle) - }); + let has_pending_resize = window_under + .with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)); if has_pending_resize { return; diff --git a/src/grab/resize_grab.rs b/src/grab/resize_grab.rs index 0cac2bb..d62bb38 100644 --- a/src/grab/resize_grab.rs +++ b/src/grab/resize_grab.rs @@ -18,7 +18,10 @@ use smithay::{ wayland::{compositor, seat::WaylandFocus, shell::xdg::SurfaceCachedState}, }; -use crate::{backend::Backend, state::State, window::SurfaceState}; +use crate::{ + backend::Backend, + state::{State, WithState}, +}; pub struct ResizeSurfaceGrab { start_data: GrabStartData, @@ -40,8 +43,8 @@ impl ResizeSurfaceGrab { initial_window_rect: Rectangle, button_used: u32, ) -> Self { - ResizeSurfaceState::with_state(window.toplevel().wl_surface(), |state| { - *state = ResizeSurfaceState::Resizing { + window.toplevel().wl_surface().with_state(|state| { + state.resize_state = ResizeSurfaceState::Resizing { edges, initial_window_rect, }; @@ -169,8 +172,8 @@ impl PointerGrab> for ResizeSurfaceGrab> { toplevel_surface.send_pending_configure(); - ResizeSurfaceState::with_state(toplevel_surface.wl_surface(), |state| { - *state = ResizeSurfaceState::WaitingForLastCommit { + toplevel_surface.wl_surface().with_state(|state| { + state.resize_state = ResizeSurfaceState::WaitingForLastCommit { edges: self.edges, initial_window_rect: self.initial_window_rect, }; @@ -193,7 +196,7 @@ impl PointerGrab> for ResizeSurfaceGrab> { } #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] -enum ResizeSurfaceState { +pub enum ResizeSurfaceState { #[default] Idle, Resizing { @@ -225,15 +228,14 @@ impl ResizeSurfaceState { } } -impl SurfaceState for ResizeSurfaceState {} - pub fn handle_commit(state: &mut State, surface: &WlSurface) -> Option<()> { let window = state.window_for_surface(surface)?; let mut window_loc = state.space.element_location(&window)?; let geometry = window.geometry(); - let new_loc: Point, Logical> = ResizeSurfaceState::with_state(surface, |state| { + let new_loc: Point, Logical> = surface.with_state(|state| { state + .resize_state .commit() .map(|(edges, initial_window_rect)| { let mut new_x: Option = None; diff --git a/src/handlers.rs b/src/handlers.rs index 1d03583..948a721 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -48,9 +48,8 @@ use smithay::{ use crate::{ backend::Backend, layout::{Layout, LayoutVec}, - output::OutputState, - state::{ClientState, State}, - window::window_state::{CommitState, WindowResizeState, WindowState}, + state::{ClientState, State, WithState}, + window::window_state::WindowResizeState, }; impl BufferHandler for State { @@ -117,22 +116,11 @@ impl CompositorHandler for State { crate::grab::resize_grab::handle_commit(self, surface); if let Some(window) = self.window_for_surface(surface) { - WindowState::with(&window, |state| { + window.with_state(|state| { if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state { state.resize_state = WindowResizeState::Idle; self.space.map_element(window.clone(), new_pos, false); } - - if let CommitState::Acked = state.needs_raise { - let clone = window.clone(); - - // FIXME: happens before the other windows ack, so it's useless - self.loop_handle.insert_idle(move |data| { - tracing::debug!("raising window"); - data.state.space.raise_element(&clone, true); - }); - state.needs_raise = CommitState::Idle; - } }); } } @@ -236,12 +224,12 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new(surface); - WindowState::with(&window, |state| { + window.with_state(|state| { state.tags = match ( &self.focus_state.focused_output, self.space.outputs().next(), ) { - (Some(output), _) | (None, Some(output)) => OutputState::with(output, |state| { + (Some(output), _) | (None, Some(output)) => output.with_state(|state| { let output_tags = state .focused_tags() .map(|tag| tag.id.clone()) @@ -276,7 +264,7 @@ impl XdgShellHandler for State { self.loop_handle.insert_idle(move |data| { if let Some(focused_output) = &data.state.focus_state.focused_output { - OutputState::with(focused_output, |state| { + focused_output.with_state(|state| { data.state .windows .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) @@ -290,7 +278,7 @@ impl XdgShellHandler for State { tracing::debug!("toplevel destroyed"); self.windows.retain(|window| window.toplevel() != &surface); if let Some(focused_output) = self.focus_state.focused_output.as_ref() { - OutputState::with(focused_output, |state| { + focused_output.with_state(|state| { self.windows .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) .layout(&self.space, focused_output); @@ -400,7 +388,7 @@ impl XdgShellHandler for State { // tracing::debug!("start of ack_configure"); if let Some(window) = self.window_for_surface(&surface) { // tracing::debug!("found window for surface"); - WindowState::with(&window, |state| { + window.with_state(|state| { if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state { match &configure { Configure::Toplevel(configure) => { @@ -412,9 +400,6 @@ impl XdgShellHandler for State { Configure::Popup(_) => todo!(), } } - if let CommitState::RequestReceived(_serial) = state.needs_raise { - state.needs_raise = CommitState::Acked; - } }); // HACK: If a window is currently going through something that generates a bunch of @@ -424,7 +409,7 @@ impl XdgShellHandler for State { // | mapping the element in commit, this means that the window won't reappear on a tag // | change. The code below is a workaround until I can figure it out. if !self.space.elements().any(|win| win == &window) { - WindowState::with(&window, |state| { + window.with_state(|state| { if let WindowResizeState::WaitingForCommit(new_loc) = state.resize_state { tracing::debug!("remapping window"); let win = window.clone(); diff --git a/src/layout.rs b/src/layout.rs index aadee71..b32faeb 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -12,9 +12,9 @@ use smithay::{ use crate::{ backend::Backend, - state::State, + state::{State, WithState}, tag::TagId, - window::window_state::{WindowResizeState, WindowState}, + window::window_state::WindowResizeState, }; pub enum Direction { @@ -24,12 +24,12 @@ pub enum Direction { Bottom, } -pub struct MasterStack { - inner: Vec, +pub trait Layout { + fn layout(&self, space: &Space, output: &Output); } -pub trait Layout<'a, S: SpaceElement> { - fn layout(&self, space: &Space, output: &Output); +pub struct MasterStack { + inner: Vec, } impl MasterStack { @@ -40,9 +40,7 @@ impl MasterStack { pub fn stack(&self) -> impl Iterator { self.inner.iter().skip(1) } -} -impl MasterStack { fn layout_stack(&self, space: &Space, output: &Output) { let stack_count = self.stack().count(); @@ -58,7 +56,7 @@ impl MasterStack { state.size = Some((output_geo.size.w / 2, height).into()); }); - WindowState::with(win, |state| { + win.with_state(|state| { state.resize_state = WindowResizeState::WaitingForAck( win.toplevel().send_configure(), (output_geo.size.w / 2, i as i32 * height).into(), @@ -68,6 +66,96 @@ impl MasterStack { } } +impl Layout for MasterStack { + fn layout(&self, space: &Space, output: &Output) { + let Some(master) = self.master() else { + return; + }; + + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + if self.stack().count() == 0 { + // one window + master.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + master.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + } else { + let new_master_size: Size = + (output_geo.size.w / 2, output_geo.size.h).into(); + master.toplevel().with_pending_state(|state| { + state.size = Some(new_master_size); + }); + master.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (0, 0).into(), + ); + }); + + self.layout_stack(space, output); + } + } +} + +pub struct Dwindle { + inner: Vec, +} + +impl Layout for Dwindle { + fn layout(&self, space: &Space, output: &Output) { + todo!() + } +} + +pub trait LayoutVec { + /// Interpret this vec as a master-stack layout. + fn to_master_stack(&self, tags: Vec) -> MasterStack; + fn to_dwindle(&self, tags: Vec) -> Dwindle; +} + +impl LayoutVec for Vec { + fn to_master_stack(&self, tags: Vec) -> MasterStack { + MasterStack { + inner: filter_windows(self, tags), + } + } + + fn to_dwindle(&self, tags: Vec) -> Dwindle { + Dwindle { + inner: filter_windows(self, tags), + } + } +} + +fn filter_windows(windows: &[Window], tags: Vec) -> Vec { + 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) { + return true; + } + } + false + } + }) + }) + .cloned() + .collect() +} + impl State { pub fn swap_window_positions(&mut self, win1: &Window, win2: &Window) { // FIXME: moving the mouse quickly will break swapping @@ -85,12 +173,12 @@ impl State { }); let serial = win1.toplevel().send_configure(); - WindowState::with(win1, |state| { + win1.with_state(|state| { state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); }); let serial = win2.toplevel().send_configure(); - WindowState::with(win2, |state| { + win2.with_state(|state| { state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); }); @@ -108,73 +196,3 @@ impl State { } } } - -impl<'a> Layout<'a, Window> for MasterStack { - fn layout(&self, space: &Space, output: &Output) { - let Some(master) = self.master() else { - return; - }; - - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - if self.stack().count() == 0 { - // one window - master.toplevel().with_pending_state(|state| { - state.size = Some(output_geo.size); - }); - - WindowState::with(master, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (0, 0).into(), - ); - }); - } else { - let new_master_size: Size = - (output_geo.size.w / 2, output_geo.size.h).into(); - master.toplevel().with_pending_state(|state| { - state.size = Some(new_master_size); - }); - WindowState::with(master, |state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (0, 0).into(), - ); - }); - - self.layout_stack(space, output); - } - } -} - -pub trait LayoutVec { - /// Interpret this vec as a master-stack layout. - fn to_master_stack(&self, tags: Vec) -> MasterStack; - // fn as_binary_tree(&mut self); TODO: -} - -impl LayoutVec for Vec { - fn to_master_stack(&self, tags: Vec) -> MasterStack { - MasterStack { - inner: self - .iter() - .filter(|window| { - WindowState::with(window, |state| { - state.floating.is_tiled() && { - for tag_id in state.tags.iter() { - if tags.iter().any(|tag| tag == tag_id) { - return true; - } - } - false - } - }) - }) - .cloned() - .collect(), - } - } -} diff --git a/src/output.rs b/src/output.rs index 71884b6..2f611d2 100644 --- a/src/output.rs +++ b/src/output.rs @@ -8,33 +8,34 @@ use std::cell::RefCell; use smithay::output::Output; -use crate::tag::Tag; +use crate::{state::WithState, tag::Tag}; #[derive(Default)] pub struct OutputState { pub tags: Vec, } +impl WithState for Output { + type State = OutputState; + + fn with_state(&self, mut func: F) -> T + where + F: FnMut(&mut Self::State) -> T, + { + self.user_data() + .insert_if_missing(RefCell::::default); + + let state = self + .user_data() + .get::>() + .expect("RefCell not in data map"); + + func(&mut state.borrow_mut()) + } +} + impl OutputState { pub fn focused_tags(&mut self) -> impl Iterator { self.tags.iter_mut().filter(|tag| tag.active) } } - -impl OutputState { - pub fn with(output: &Output, mut func: F) -> T - where - F: FnMut(&mut Self) -> T, - { - output - .user_data() - .insert_if_missing(RefCell::::default); - - let mut state = output - .user_data() - .get::>() - .expect("RefCell not in data map"); - - func(&mut state.borrow_mut()) - } -} diff --git a/src/state.rs b/src/state.rs index 51f5216..190655b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,6 +5,7 @@ // SPDX-License-Identifier: MPL-2.0 use std::{ + cell::RefCell, error::Error, ffi::OsString, os::{fd::AsRawFd, unix::net::UnixStream}, @@ -19,13 +20,10 @@ use crate::{ PinnacleSocketSource, }, focus::FocusState, + grab::resize_grab::ResizeSurfaceState, layout::{Layout, LayoutVec}, - output::OutputState, tag::Tag, - window::{ - window_state::{WindowResizeState, WindowState}, - WindowProperties, - }, + window::{window_state::WindowResizeState, WindowProperties}, }; use calloop::futures::Scheduler; use futures_lite::AsyncBufReadExt; @@ -47,6 +45,7 @@ use smithay::{ }, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, + protocol::wl_surface::WlSurface, Display, }, }, @@ -138,7 +137,7 @@ impl State { Msg::SetWindowSize { window_id, size } => { let Some(window) = self.space.elements().find(|&win| { - WindowState::with(win, |state| state.id == window_id) + win.with_state( |state| state.id == window_id) }) else { return; }; // TODO: tiled vs floating @@ -151,18 +150,19 @@ impl State { if let Some(window) = self .windows .iter() - .find(|&win| WindowState::with(win, |state| state.id == window_id)) + .find(|&win| win.with_state(|state| state.id == window_id)) { - WindowState::with(window, |state| { - OutputState::with( - &self.focus_state.focused_output.as_ref().unwrap(), - |op_state| { + window.with_state(|state| { + self.focus_state + .focused_output + .as_ref() + .unwrap() + .with_state(|op_state| { let tag = op_state.tags.iter().find(|tag| tag.name == tag_id); if let Some(tag) = tag { state.tags = vec![tag.id.clone()]; } - }, - ); + }); }); } @@ -172,12 +172,14 @@ impl State { if let Some(window) = self .windows .iter() - .find(|&win| WindowState::with(win, |state| state.id == window_id)) + .find(|&win| win.with_state(|state| state.id == window_id)) { - WindowState::with(window, |state| { - OutputState::with( - &self.focus_state.focused_output.as_ref().unwrap(), - |op_state| { + window.with_state(|state| { + self.focus_state + .focused_output + .as_ref() + .unwrap() + .with_state(|op_state| { let tag = op_state.tags.iter().find(|tag| tag.name == tag_id); if let Some(tag) = tag { if state.tags.contains(&tag.id) { @@ -186,50 +188,54 @@ impl State { state.tags.push(tag.id.clone()); } } - }, - ); + }); }); self.re_layout(); } } Msg::ToggleTag { tag_id } => { - OutputState::with( - self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error - |state| { + 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; } - }, - ); + }); self.re_layout(); } Msg::SwitchToTag { tag_id } => { - OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| { - if !state.tags.iter().any(|tag| tag.name == tag_id) { - // TODO: notify error - return; - } - for tag in state.tags.iter_mut() { - tag.active = false; - } + self.focus_state + .focused_output + .as_ref() + .unwrap() + .with_state(|state| { + if !state.tags.iter().any(|tag| tag.name == tag_id) { + // TODO: notify error + return; + } + for tag in state.tags.iter_mut() { + tag.active = false; + } - let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) else { + let Some(tag) = state.tags.iter_mut().find(|tag| tag.name == tag_id) else { unreachable!() }; - tag.active = true; + tag.active = true; - tracing::debug!( - "focused tags: {:?}", - state - .tags - .iter() - .filter(|tag| tag.active) - .map(|tag| &tag.name) - .collect::>() - ); - }); + tracing::debug!( + "focused tags: {:?}", + state + .tags + .iter() + .filter(|tag| tag.active) + .map(|tag| &tag.name) + .collect::>() + ); + }); self.re_layout(); } @@ -241,7 +247,7 @@ impl State { .as_ref() .or_else(|| self.space.outputs().next()) { - OutputState::with(output, |state| { + output.with_state(|state| { state.tags.extend( tags.clone() .into_iter() @@ -252,7 +258,7 @@ impl State { } Msg::RemoveTags { tags } => { for output in self.space.outputs() { - OutputState::with(output, |state| { + output.with_state(|state| { state.tags.retain(|tag| !tags.contains(&tag.name)); }); } @@ -277,9 +283,8 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = WindowState::with(¤t_focus, |state| { - (state.id, state.floating.is_floating()) - }); + let (window_id, floating) = + current_focus.with_state(|state| (state.id, state.floating.is_floating())); // TODO: unwrap let location = self.space.element_location(¤t_focus).unwrap(); let props = WindowProperties { @@ -320,9 +325,8 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = WindowState::with(win, |state| { - (state.id, state.floating.is_floating()) - }); + let (window_id, floating) = + win.with_state(|state| (state.id, state.floating.is_floating())); // TODO: unwrap let location = self .space @@ -511,12 +515,12 @@ impl State { pub fn re_layout(&mut self) { let output = self.focus_state.focused_output.as_ref().unwrap(); - let (render, do_not_render) = OutputState::with(output, |state| { + let (render, do_not_render) = output.with_state(|state| { self.windows .to_master_stack(state.focused_tags().map(|tag| tag.id.clone()).collect()) .layout(&self.space, output); self.windows.iter().cloned().partition::, _>(|win| { - WindowState::with(win, |win_state| { + win.with_state(|win_state| { if win_state.floating.is_floating() { return true; } @@ -547,12 +551,11 @@ impl State { tracing::debug!("running idle cb"); tracing::debug!("win len is {}", windows.len()); for window in windows.iter() { - WindowState::with(window, |state| { + window.with_state(|state| { tracing::debug!("win state is {:?}", state.resize_state); }); - if WindowState::with(window, |state| { - !matches!(state.resize_state, WindowResizeState::Idle) - }) { + if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) + { tracing::debug!("some windows not idle"); data.state.loop_handle.insert_idle(|data| { data.state.schedule_on_commit(windows, on_commit); @@ -784,3 +787,36 @@ impl ApiState { Default::default() } } + +pub trait WithState { + type State: Default; + fn with_state(&self, func: F) -> T + where + F: FnMut(&mut Self::State) -> T; +} + +#[derive(Default, Debug)] +pub struct WlSurfaceState { + pub resize_state: ResizeSurfaceState, +} + +impl WithState for WlSurface { + type State = WlSurfaceState; + + fn with_state(&self, mut func: F) -> T + where + F: FnMut(&mut Self::State) -> T, + { + compositor::with_states(self, |states| { + states + .data_map + .insert_if_missing(RefCell::::default); + let state = states + .data_map + .get::>() + .expect("This should never happen"); + + func(&mut state.borrow_mut()) + }) + } +} diff --git a/src/window.rs b/src/window.rs index b3c2ffd..e13982b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,43 +4,20 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::cell::RefCell; - use smithay::{ - desktop::Window, - reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::{compositor, seat::WaylandFocus}, + desktop::Window, reexports::wayland_server::protocol::wl_surface::WlSurface, + wayland::seat::WaylandFocus, }; -use crate::{backend::Backend, output::OutputState, state::State}; +use crate::{ + backend::Backend, + state::{State, WithState}, +}; -use self::window_state::{Float, WindowId, WindowState}; +use self::window_state::{Float, WindowId}; pub mod window_state; -// TODO: maybe get rid of this and move the fn into resize_surface state because it's the only user -pub trait SurfaceState: Default + 'static { - /// Access the [`SurfaceState`] associated with a [`WlSurface`]. - /// - /// # Panics - /// - /// This function will panic if you use it within itself due to the use of a [`RefCell`]. - fn with_state(wl_surface: &WlSurface, function: F) -> T - where - F: FnOnce(&mut Self) -> T, - { - compositor::with_states(wl_surface, |states| { - states.data_map.insert_if_missing(RefCell::::default); - let state = states - .data_map - .get::>() - .expect("This should never happen"); - - function(&mut state.borrow_mut()) - }) - } -} - impl State { /// Returns the [Window] associated with a given [WlSurface]. pub fn window_for_surface(&self, surface: &WlSurface) -> Option { @@ -59,7 +36,7 @@ impl State { /// Toggle a window's floating status. pub fn toggle_floating(state: &mut State, window: &Window) { - WindowState::with(window, |window_state| { + window.with_state(|window_state| { match window_state.floating { Float::Tiled(prev_loc_and_size) => { if let Some((prev_loc, prev_size)) = prev_loc_and_size { @@ -88,13 +65,13 @@ pub fn toggle_floating(state: &mut State, window: &Window) { state.re_layout(); let output = state.focus_state.focused_output.as_ref().unwrap(); - let render = OutputState::with(output, |op_state| { + let render = output.with_state(|op_state| { state .windows .iter() .cloned() .filter(|win| { - WindowState::with(win, |win_state| { + win.with_state(|win_state| { if win_state.floating.is_floating() { return true; } diff --git a/src/window/window_state.rs b/src/window/window_state.rs index b40e6a9..b94c4bd 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -14,7 +14,7 @@ use smithay::{ utils::{Logical, Point, Serial, Size}, }; -use crate::tag::TagId; +use crate::{state::WithState, tag::TagId}; #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct WindowId(u32); @@ -37,15 +37,6 @@ pub struct WindowState { pub resize_state: WindowResizeState, /// What tags the window is currently on. pub tags: Vec, - - // FIXME: this is a bandaid to get floating working again, figure out an actual solution - pub needs_raise: CommitState, -} - -pub enum CommitState { - Idle, - RequestReceived(Serial), - Acked, } /// The state of a window's resize operation. @@ -116,20 +107,23 @@ impl WindowState { pub fn new() -> Self { Default::default() } +} - /// Access a [Window]'s state, optionally returning something. - pub fn with(window: &Window, mut func: F) -> T +impl WithState for Window { + type State = WindowState; + + fn with_state(&self, mut func: F) -> T where - F: FnMut(&mut Self) -> T, + F: FnMut(&mut Self::State) -> T, { - window - .user_data() - .insert_if_missing(RefCell::::default); + self.user_data() + .insert_if_missing(RefCell::::default); - let state = window + let state = self .user_data() - .get::>() - .expect("This should never happen"); + .get::>() + .expect("RefCell not in data map"); + func(&mut state.borrow_mut()) } } @@ -142,7 +136,6 @@ impl Default for WindowState { floating: Float::Tiled(None), resize_state: WindowResizeState::Idle, tags: vec![], - needs_raise: CommitState::Idle, } } } From d91c06dbe9a278754e590642b20b5330e034e4c6 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 11 Jul 2023 11:59:38 -0500 Subject: [PATCH 12/27] Add output to API --- api/lua/example_config.lua | 18 ++++- api/lua/input.lua | 12 +++- api/lua/keys.lua | 4 +- api/lua/msg.lua | 27 ++++++-- api/lua/output.lua | 133 +++++++++++++++++++++++++++++++++++++ api/lua/pinnacle.lua | 11 +-- api/lua/tag.lua | 46 +++++++++++-- api/lua/window.lua | 28 +++----- src/api/msg.rs | 54 +++++++++------ src/backend/udev.rs | 32 +++++++-- src/input.rs | 12 ++-- src/state.rs | 133 ++++++++++++++++++++++++++++++++----- src/tag.rs | 7 +- 13 files changed, 415 insertions(+), 102 deletions(-) create mode 100644 api/lua/output.lua diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 21a3844..93b51db 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -11,10 +11,11 @@ pcall(require, "luarocks.loader") -- Neovim users be like: require("pinnacle").setup(function(pinnacle) - local input = pinnacle.input -- Key and mouse binds + local input = pinnacle.input -- Key and mouse binds local window = pinnacle.window -- Window management local process = pinnacle.process -- Process spawning - local tag = pinnacle.tag -- Tag management + local tag = pinnacle.tag -- Tag management + local output = pinnacle.output -- Output management -- Every key supported by xkbcommon. -- Support for just putting in a string of a key is intended. @@ -27,6 +28,7 @@ require("pinnacle").setup(function(pinnacle) local terminal = "alacritty" -- Keybinds ---------------------------------------------------------------------- + input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit) input.keybind({ mod_key, "Alt" }, keys.c, window.close_window) @@ -49,8 +51,18 @@ require("pinnacle").setup(function(pinnacle) process.spawn("nautilus") end) + input.keybind({ mod_key }, keys.g, function() + local op = output.get_by_res(2560, 1440) + for _, v in pairs(op) do + print(v.name) + end + end) + -- Tags --------------------------------------------------------------------------- - tag.add("1", "2", "3", "4", "5") + + output.connect_for_all(function(op) + tag.add(op, "1", "2", "3", "4", "5") + end) tag.toggle("1") input.keybind({ mod_key }, keys.KEY_1, function() diff --git a/api/lua/input.lua b/api/lua/input.lua index a362ee0..7d687dd 100644 --- a/api/lua/input.lua +++ b/api/lua/input.lua @@ -9,9 +9,19 @@ local input = { } ---Set a keybind. If called with an already existing keybind, it gets replaced. +--- +---# Example +--- +---```lua +----- The following sets Super + Return to open Alacritty +--- +---input.keybind({ "Super" }, input.keys.Return, function() +--- process.spawn("Alacritty") +---end) +---``` ---@param key Keys The key for the keybind. ---@param modifiers (Modifier)[] Which modifiers need to be pressed for the keybind to trigger. ----@param action fun() What to run. +---@param action fun() What to do. function input.keybind(modifiers, key, action) table.insert(CallbackTable, action) SendMsg({ diff --git a/api/lua/keys.lua b/api/lua/keys.lua index 88286e0..41113ec 100644 --- a/api/lua/keys.lua +++ b/api/lua/keys.lua @@ -7,7 +7,7 @@ ---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super" ---@enum Keys -local M = { +local keys = { NoSymbol = 0x00000000, VoidSymbol = 0x00ffffff, @@ -4321,4 +4321,4 @@ local M = { block = 0x100000fc, } -return M +return keys diff --git a/api/lua/msg.lua b/api/lua/msg.lua index c56ea11..b4fee45 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -7,7 +7,7 @@ ---@meta _ ---@class _Msg ----@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer } +---@field SetKeybind { key: Keys, modifiers: Modifier[], callback_id: integer } ---@field SetMousebind { button: integer } --Windows ---@field CloseWindow { client_id: integer? } @@ -21,25 +21,38 @@ --Tags ---@field ToggleTag { tag_id: string } ---@field SwitchToTag { tag_id: string } ----@field AddTags { tags: string[] } ----@field RemoveTags { tags: string[] } +---@field AddTags { output_name: string, tags: string[] } +---@field RemoveTags { output_name: string, tags: string[] } +--Outputs +---@field ConnectForAllOutputs { callback_id: integer } ---@alias Msg _Msg | "Quit" ----@class Request ----@field GetWindowByFocus { id: integer } ----@field GetAllWindows { id: integer } +-------------------------------------------------------------------------------------------- + +---@class _Request +--Windows +---@field GetWindowByAppId { app_id: string } +---@field GetWindowByTitle { title: string } +--Outputs +---@field GetOutputByName { name: string } +---@field GetOutputsByModel { model: string } +---@field GetOutputsByRes { res: integer[] } + +---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" ---@class IncomingMsg ---@field CallCallback { callback_id: integer, args: Args } ----@field RequestResponse { request_id: integer, response: RequestResponse } +---@field RequestResponse { response: RequestResponse } ---@class Args ---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? } +---@field ConnectForAllOutputs { output_name: string } ---@class RequestResponse ---@field Window { window: WindowProperties } ---@field GetAllWindows { windows: WindowProperties[] } +---@field Outputs { names: string[] } ---@class WindowProperties ---@field id integer diff --git a/api/lua/output.lua b/api/lua/output.lua new file mode 100644 index 0000000..2cad4ed --- /dev/null +++ b/api/lua/output.lua @@ -0,0 +1,133 @@ +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, You can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- SPDX-License-Identifier: MPL-2.0 + +---@class Output A display. +---@field name string The name of this output (or rather, of its connector). +local op = {} + +---Add methods to this output. +---@param props Output +---@return Output +local function new_output(props) + -- Copy functions over + for k, v in pairs(op) do + props[k] = v + end + + return props +end + +------------------------------------------------------ + +local output = {} + +---Get an output by its name. +--- +---"Name" in this sense does not mean its model or manufacturer; +---rather, "name" is the name of the connector the output is connected to. +---This should be something like "HDMI-A-0", "eDP-1", or similar. +--- +---# Examples +---```lua +---local monitor = output.get_by_name("DP-1") +---print(monitor.name) -- should print `DP-1` +---``` +---@param name string The name of the output. +---@return Output|nil +function output.get_by_name(name) + SendMsg({ + Request = { + GetOutputByName = { + name = name, + }, + }, + }) + + 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 + +---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. +--- +---Get outputs by their model. +---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays. +---@param model string The model of the output(s). +---@return Output[] outputs All outputs with this model. If there are none, the returned table will be empty. +function output.get_by_model(model) + SendMsg({ + Request = { + GetOutputsByModel = { + model = model, + }, + }, + }) + + local response = ReadMsg() + + local names = response.RequestResponse.response.Outputs.names + + ---@type Output + local outputs = {} + for _, v in pairs(names) do + table.insert(outputs, new_output({ name = v })) + end + + return outputs +end + +---Get outputs by their resolution. +--- +---@param width integer The width of the outputs, in pixels. +---@param height integer The height of the outputs, in pixels. +---@return Output[] outputs All outputs with this resolution. If there are none, the returned table will be empty. +function output.get_by_res(width, height) + SendMsg({ + Request = { + GetOutputsByRes = { + res = { width, height }, + }, + }, + }) + + local response = ReadMsg() + + local names = response.RequestResponse.response.Outputs.names + + ---@type Output + local outputs = {} + for _, v in pairs(names) do + table.insert(outputs, new_output({ name = v })) + end + + return outputs +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. +---@param func fun(output: Output) The function that will be run. +function output.connect_for_all(func) + ---@param args Args + table.insert(CallbackTable, function(args) + local args = args.ConnectForAllOutputs + func(new_output({ name = args.output_name })) + end) + SendMsg({ + ConnectForAllOutputs = { + callback_id = #CallbackTable, + }, + }) +end + +return output diff --git a/api/lua/pinnacle.lua b/api/lua/pinnacle.lua index 506a317..7cc0965 100644 --- a/api/lua/pinnacle.lua +++ b/api/lua/pinnacle.lua @@ -56,6 +56,8 @@ local pinnacle = { process = require("process"), ---Tag management tag = require("tag"), + ---Output management + output = require("output"), } ---Quit Pinnacle. @@ -114,15 +116,6 @@ function pinnacle.setup(config_func) return tb end - Requests = { - id = 1, - } - function Requests:next() - local id = self.id - self.id = self.id + 1 - return id - end - config_func(pinnacle) while true do diff --git a/api/lua/tag.lua b/api/lua/tag.lua index decce6f..6714431 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -8,36 +8,62 @@ local tag = {} ---Add tags. --- ----If you need to add the strings in a table, use `tag.add_table` instead. +---If you need to add the names as a table, use `tag.add_table` instead. --- ---# Example --- ---```lua ----tag.add("1", "2", "3", "4", "5") -- Add tags with names 1-5 +---local output = output.get_by_name("DP-1") +---if output ~= nil then +--- tag.add(output, "1", "2", "3", "4", "5") -- Add tags with names 1-5 +---end ---``` +---@param output Output The output you want these tags to be added to. ---@param ... string The names of the new tags you want to add. -function tag.add(...) +function tag.add(output, ...) local tags = table.pack(...) tags["n"] = nil SendMsg({ AddTags = { + output_name = output.name, tags = tags, }, }) end ---Like `tag.add`, but with a table of strings instead. +--- +---# Example +--- +---```lua +---local tags = { "Terminal", "Browser", "Mail", "Gaming", "Potato" } +---local output = output.get_by_name("DP-1") +---if output ~= nil then +--- tag.add(output, tags) -- Add tags with the names above +---end +---``` +---@param output Output The output you want these tags to be added to. ---@param tags string[] The names of the new tags you want to add, as a table. -function tag.add_table(tags) +function tag.add_table(output, tags) SendMsg({ AddTags = { + output_name = output.name, tags = tags, }, }) end ----Toggle a tag's display. +---Toggle a tag on the currently focused output. +--- +---# Example +--- +---```lua +----- Assuming all tags are toggled off... +---tag.toggle("1") +---tag.toggle("2") +----- 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({ @@ -47,7 +73,15 @@ function tag.toggle(name) }) end ----Switch to a tag, deactivating any other active tags. +---Switch to a tag on the currently focused output, deactivating any other active tags on that output. +--- +---This is used to replicate what a traditional workspace is on some other Wayland compositors. +--- +---# Example +--- +---```lua +---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({ diff --git a/api/lua/window.lua b/api/lua/window.lua index 810e2c7..bd7982a 100644 --- a/api/lua/window.lua +++ b/api/lua/window.lua @@ -13,7 +13,7 @@ ---@field private floating boolean Whether the window is floating or not (tiled) local win = {} ----@param props { id: integer, app_id: string?, title: string?, size: { w: integer, h: integer }, location: { x: integer, y: integer }, floating: boolean } +---@param props Window ---@return Window local function new_window(props) -- Copy functions over @@ -91,15 +91,14 @@ function window.toggle_floating(client_id) }) end +---TODO: This function is not implemented yet. +--- ---Get a window by its app id (aka its X11 class). ---@param app_id string The window's app id. For example, Alacritty's app id is "Alacritty". ---@return Window window -- TODO: nil function window.get_by_app_id(app_id) - local req_id = Requests:next() - SendRequest({ GetWindowByAppId = { - id = req_id, app_id = app_id, }, }) @@ -127,15 +126,14 @@ function window.get_by_app_id(app_id) return new_window(wind) end +---TODO: This function is not implemented yet. +--- ---Get a window by its title. ---@param title string The window's title. ---@return Window function window.get_by_title(title) - local req_id = Requests:next() - SendRequest({ GetWindowByTitle = { - id = req_id, title = title, }, }) @@ -166,13 +164,7 @@ end ---Get the currently focused window. ---@return Window function window.get_focused() - local req_id = Requests:next() - - SendRequest({ - GetWindowByFocus = { - id = req_id, - }, - }) + SendRequest("GetWindowByFocus") local response = ReadMsg() @@ -199,12 +191,8 @@ end ---Get all windows. ---@return Window[] -function window.get_windows() - SendRequest({ - GetAllWindows = { - id = Requests:next(), - }, - }) +function window.get_all() + SendRequest("GetAllWindows") -- INFO: these read synchronously so this should always work IF the server works correctly diff --git a/src/api/msg.rs b/src/api/msg.rs index 5fca1f2..850dabe 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -17,7 +17,7 @@ pub enum Msg { // Input SetKeybind { key: u32, - modifiers: Vec, + modifiers: Vec, callback_id: CallbackId, }, SetMousebind { @@ -47,20 +47,30 @@ pub enum Msg { }, // Tag management + // FIXME: tag_id should not be a string ToggleTag { tag_id: String, }, + // FIXME: tag_id should not be a string SwitchToTag { tag_id: String, }, AddTags { + /// The name of the output you want these tags on. + output_name: String, tags: Vec, }, RemoveTags { - // TODO: + /// The name of the output you want these tags removed from. + output_name: String, tags: Vec, }, + // Output management + ConnectForAllOutputs { + callback_id: CallbackId, + }, + // Process management /// Spawn a program with an optional callback. Spawn { @@ -82,14 +92,17 @@ pub struct RequestId(pub u32); #[derive(Debug, serde::Serialize, serde::Deserialize)] /// Messages that require a server response, usually to provide some data. pub enum Request { - GetWindowByAppId { id: RequestId, app_id: String }, - GetWindowByTitle { id: RequestId, title: String }, - GetWindowByFocus { id: RequestId }, - GetAllWindows { id: RequestId }, + GetWindowByAppId { app_id: String }, + GetWindowByTitle { title: String }, + GetWindowByFocus, + GetAllWindows, + GetOutputByName { name: String }, + GetOutputsByModel { model: String }, + GetOutputsByRes { res: (u32, u32) }, } #[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] -pub enum Modifiers { +pub enum Modifier { Shift = 0b0000_0001, Ctrl = 0b0000_0010, Alt = 0b0000_0100, @@ -100,7 +113,7 @@ pub enum Modifiers { #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub struct ModifierMask(u8); -impl> From for ModifierMask { +impl> From for ModifierMask { fn from(value: T) -> Self { let value = value.into_iter(); let mut mask: u8 = 0b0000_0000; @@ -112,19 +125,19 @@ impl> From for ModifierMask { } impl ModifierMask { - pub fn values(self) -> Vec { - let mut res = Vec::::new(); - if self.0 & Modifiers::Shift as u8 == Modifiers::Shift as u8 { - res.push(Modifiers::Shift); + pub fn values(self) -> Vec { + let mut res = Vec::::new(); + if self.0 & Modifier::Shift as u8 == Modifier::Shift as u8 { + res.push(Modifier::Shift); } - if self.0 & Modifiers::Ctrl as u8 == Modifiers::Ctrl as u8 { - res.push(Modifiers::Ctrl); + if self.0 & Modifier::Ctrl as u8 == Modifier::Ctrl as u8 { + res.push(Modifier::Ctrl); } - if self.0 & Modifiers::Alt as u8 == Modifiers::Alt as u8 { - res.push(Modifiers::Alt); + if self.0 & Modifier::Alt as u8 == Modifier::Alt as u8 { + res.push(Modifier::Alt); } - if self.0 & Modifiers::Super as u8 == Modifiers::Super as u8 { - res.push(Modifiers::Super); + if self.0 & Modifier::Super as u8 == Modifier::Super as u8 { + res.push(Modifier::Super); } res } @@ -139,7 +152,6 @@ pub enum OutgoingMsg { args: Option, }, RequestResponse { - request_id: RequestId, response: RequestResponse, }, } @@ -157,10 +169,14 @@ pub enum Args { #[serde(default)] exit_msg: Option, }, + ConnectForAllOutputs { + output_name: String, + }, } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum RequestResponse { Window { window: WindowProperties }, GetAllWindows { windows: Vec }, + Outputs { names: Vec }, } diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 75faa4e..9821946 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -97,6 +97,7 @@ use smithay_drm_extras::{ }; use crate::{ + api::msg::{Args, OutgoingMsg}, render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, state::{take_presentation_feedback, CalloopData, State, SurfaceDmabufFeedback}, }; @@ -226,11 +227,6 @@ pub fn run_udev() -> Result<(), Box> { pointer_element: PointerElement::default(), }; - // - // - // - // - let mut state = State::::init( data, &mut display, @@ -852,6 +848,32 @@ impl State { device_id: node, }); + // Run any connected callbacks + { + let clone = output.clone(); + self.loop_handle.insert_idle(move |data| { + let stream = data + .state + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist"); + let mut stream = stream.lock().expect("Couldn't lock stream"); + for callback_id in data.state.output_callback_ids.iter() { + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::CallCallback { + callback_id: *callback_id, + args: Some(Args::ConnectForAllOutputs { + output_name: clone.name(), + }), + }, + ) + .expect("Send to client failed"); + } + }); + } + let allocator = GbmAllocator::new( device.gbm.clone(), GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, diff --git a/src/input.rs b/src/input.rs index 2386018..e0bb554 100644 --- a/src/input.rs +++ b/src/input.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; -use crate::api::msg::{CallbackId, ModifierMask, Modifiers, OutgoingMsg}; +use crate::api::msg::{CallbackId, Modifier, ModifierMask, OutgoingMsg}; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, @@ -221,18 +221,18 @@ impl State { time, |state, modifiers, keysym| { if press_state == KeyState::Pressed { - let mut modifier_mask = Vec::::new(); + let mut modifier_mask = Vec::::new(); if modifiers.alt { - modifier_mask.push(Modifiers::Alt); + modifier_mask.push(Modifier::Alt); } if modifiers.shift { - modifier_mask.push(Modifiers::Shift); + modifier_mask.push(Modifier::Shift); } if modifiers.ctrl { - modifier_mask.push(Modifiers::Ctrl); + modifier_mask.push(Modifier::Ctrl); } if modifiers.logo { - modifier_mask.push(Modifiers::Super); + modifier_mask.push(Modifier::Super); } let raw_sym = if keysym.raw_syms().len() == 1 { keysym.raw_syms()[0] diff --git a/src/state.rs b/src/state.rs index 190655b..ae1dc05 100644 --- a/src/state.rs +++ b/src/state.rs @@ -98,6 +98,10 @@ pub struct State { pub windows: Vec, pub async_scheduler: Scheduler<()>, + + // TODO: move into own struct + // | basically just clean this mess up + pub output_callback_ids: Vec, } impl State { @@ -240,38 +244,59 @@ impl State { self.re_layout(); } // TODO: add output - Msg::AddTags { tags } => { + Msg::AddTags { output_name, tags } => { if let Some(output) = self - .focus_state - .focused_output - .as_ref() - .or_else(|| self.space.outputs().next()) + .space + .outputs() + .find(|output| output.name() == output_name) { output.with_state(|state| { - state.tags.extend( - tags.clone() - .into_iter() - .map(|name| Tag::new(name, output.clone())), - ); + state.tags.extend(tags.iter().cloned().map(Tag::new)); }); } } - Msg::RemoveTags { tags } => { - for output in self.space.outputs() { + Msg::RemoveTags { output_name, tags } => { + if let Some(output) = self + .space + .outputs() + .find(|output| output.name() == output_name) + { output.with_state(|state| { state.tags.retain(|tag| !tags.contains(&tag.name)); }); } } + Msg::ConnectForAllOutputs { callback_id } => { + let stream = self + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist"); + let mut stream = stream.lock().expect("Couldn't lock stream"); + for output in self.space.outputs() { + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::CallCallback { + callback_id, + args: Some(Args::ConnectForAllOutputs { + output_name: output.name(), + }), + }, + ) + .expect("Send to client failed"); + } + self.output_callback_ids.push(callback_id); + } + Msg::Quit => { self.loop_signal.stop(); } Msg::Request(request) => match request { - Request::GetWindowByAppId { id, app_id } => todo!(), - Request::GetWindowByTitle { id, title } => todo!(), - Request::GetWindowByFocus { id } => { + Request::GetWindowByAppId { app_id } => todo!(), + Request::GetWindowByTitle { title } => todo!(), + Request::GetWindowByFocus => { let Some(current_focus) = self.focus_state.current_focus() else { return; }; let (app_id, title) = compositor::with_states(current_focus.toplevel().wl_surface(), |states| { @@ -304,13 +329,12 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { - request_id: id, response: RequestResponse::Window { window: props }, }, ) .expect("Send to client failed"); } - Request::GetAllWindows { id } => { + Request::GetAllWindows => { let window_props = self .space .elements() @@ -353,7 +377,6 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { - request_id: id, response: RequestResponse::GetAllWindows { windows: window_props, }, @@ -361,6 +384,78 @@ impl State { ) .expect("Couldn't send to client"); } + Request::GetOutputByName { name } => { + let names = self + .space + .outputs() + .filter(|output| output.name() == name) + .map(|output| output.name()) + .collect::>(); + 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(); + } + Request::GetOutputsByModel { model } => { + let names = self + .space + .outputs() + .filter(|output| output.physical_properties().model == model) + .map(|output| output.name()) + .collect::>(); + 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(); + } + Request::GetOutputsByRes { res } => { + let names = self + .space + .outputs() + .filter_map(|output| { + if let Some(mode) = output.current_mode() { + if mode.size == (res.0 as i32, res.1 as i32).into() { + Some(output.name()) + } else { + None + } + } else { + None + } + }) + .collect::>(); + 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(); + } }, } } @@ -718,6 +813,7 @@ impl State { async_scheduler: sched, windows: vec![], + output_callback_ids: vec![], }) } } @@ -779,6 +875,7 @@ pub fn take_presentation_feedback( /// State containing the config API's stream. #[derive(Default)] pub struct ApiState { + // TODO: this may not need to be in an arc mutex because of the move to async pub stream: Option>>, } diff --git a/src/tag.rs b/src/tag.rs index 63955c5..2f218c8 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -9,8 +9,6 @@ use std::{ sync::atomic::{AtomicU32, Ordering}, }; -use smithay::output::Output; - static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0); #[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] @@ -28,19 +26,16 @@ pub struct Tag { pub id: TagId, /// The name of this tag. pub name: String, - /// The output that this tag should be on. - pub output: Output, /// Whether this tag is active or not. pub active: bool, // TODO: layout } impl Tag { - pub fn new(name: String, output: Output) -> Self { + pub fn new(name: String) -> Self { Self { id: TagId::next(), name, - output, active: false, } } From 3efdb9d73ffc07f67fb74db0a0c5e5866dbab2f4 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 11 Jul 2023 16:10:31 -0500 Subject: [PATCH 13/27] Update output in API, modify tag tracking --- api/lua/example_config.lua | 2 +- api/lua/msg.lua | 6 +- api/lua/output.lua | 23 +++++++- api/lua/tag.lua | 62 ++++++++++++++----- src/api/msg.rs | 7 ++- src/backend/winit.rs | 2 +- src/handlers.rs | 14 +++-- src/layout.rs | 34 +++++++---- src/output.rs | 5 +- src/state.rs | 118 ++++++++++++++++++++++++------------- src/tag.rs | 61 ++++++++++++++++--- src/window.rs | 7 ++- src/window/window_state.rs | 4 +- 13 files changed, 251 insertions(+), 94 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 93b51db..08131b9 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -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") diff --git a/api/lua/msg.lua b/api/lua/msg.lua index b4fee45..b03b1a7 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -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 } diff --git a/api/lua/output.lua b/api/lua/output.lua index 2cad4ed..eacf64b 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -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 diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 6714431..9e648f8 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -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 diff --git a/src/api/msg.rs b/src/api/msg.rs index 850dabe..c2f11ec 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -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)] diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 3a03f83..f9ead41 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -219,7 +219,7 @@ pub fn run_winit() -> Result<(), Box> { None, None, ); - state.re_layout(); + state.re_layout(&output); } WinitEvent::Focus(_) => {} WinitEvent::Input(input_evt) => { diff --git a/src/handlers.rs b/src/handlers.rs index 948a721..c010080 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -232,12 +232,12 @@ impl XdgShellHandler for State { (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::>(); 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 XdgShellHandler for State { 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 XdgShellHandler for State { 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); }); } diff --git a/src/layout.rs b/src/layout.rs index b32faeb..75904eb 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -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 { pub struct MasterStack { inner: Vec, + output: Output, } impl MasterStack { @@ -49,6 +50,8 @@ impl MasterStack { 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 { 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 for MasterStack { 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 for MasterStack { 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 for MasterStack { 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 for MasterStack { pub struct Dwindle { inner: Vec, + output: Output, } impl Layout for Dwindle { @@ -119,32 +129,34 @@ impl Layout for Dwindle { pub trait LayoutVec { /// Interpret this vec as a master-stack layout. - fn to_master_stack(&self, tags: Vec) -> MasterStack; - fn to_dwindle(&self, tags: Vec) -> Dwindle; + fn to_master_stack(&self, output: &Output, tags: Vec) -> MasterStack; + fn to_dwindle(&self, output: &Output, tags: Vec) -> Dwindle; } impl LayoutVec for Vec { - fn to_master_stack(&self, tags: Vec) -> MasterStack { + fn to_master_stack(&self, output: &Output, tags: Vec) -> MasterStack { MasterStack { inner: filter_windows(self, tags), + output: output.clone(), // TODO: get rid of? } } - fn to_dwindle(&self, tags: Vec) -> Dwindle { + fn to_dwindle(&self, output: &Output, tags: Vec) -> Dwindle { Dwindle { inner: filter_windows(self, tags), + output: output.clone(), } } } -fn filter_windows(windows: &[Window], tags: Vec) -> Vec { +fn filter_windows(windows: &[Window], tags: Vec) -> Vec { 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; } } diff --git a/src/output.rs b/src/output.rs index 2f611d2..6d2e8bc 100644 --- a/src/output.rs +++ b/src/output.rs @@ -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, @@ -36,6 +39,6 @@ impl WithState for Output { impl OutputState { pub fn focused_tags(&mut self) -> impl Iterator { - self.tags.iter_mut().filter(|tag| tag.active) + self.tags.iter_mut().filter(|tag| tag.active()) } } diff --git a/src/state.rs b/src/state.rs index ae1dc05..b1130b1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -162,15 +162,16 @@ impl State { .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 State { .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::>() ); }); - - self.re_layout(); + self.re_layout(&output); + } } // TODO: add output Msg::AddTags { output_name, tags } => { @@ -251,7 +254,10 @@ impl State { .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 State { .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 State { ) .unwrap(); } + Request::GetOutputByFocus => { + let names = self + .focus_state + .focused_output + .as_ref() + .map(|output| output.name()) + .into_iter() + .collect::>(); + 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 State { } } - 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::>(); 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::, _>(|win| { + + windows.iter().cloned().partition::, _>(|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; } } diff --git a/src/tag.rs b/src/tag.rs index 2f218c8..c418f0e 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -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>); + +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 State { + pub fn output_for_tag(&self, tag: &Tag) -> Option { + self.space + .outputs() + .find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == tag))) + .cloned() } } diff --git a/src/window.rs b/src/window.rs index e13982b..6dda1bc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -62,7 +62,8 @@ pub fn toggle_floating(state: &mut State, 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(state: &mut State, 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; } } diff --git a/src/window/window_state.rs b/src/window/window_state.rs index b94c4bd..3bd9032 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -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, + pub tags: Vec, } /// The state of a window's resize operation. From 6f57d8d41338aedc6cc98b0f2638431772ae2298 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 11 Jul 2023 21:07:51 -0500 Subject: [PATCH 14/27] Add dwindle layout --- api/lua/output.lua | 2 +- src/api/msg.rs | 3 +- src/handlers.rs | 33 +++--- src/layout.rs | 262 ++++++++++++++++++++++++++++----------------- src/output.rs | 4 +- src/state.rs | 20 ++-- src/tag.rs | 9 +- src/xdg/request.rs | 10 +- 8 files changed, 209 insertions(+), 134 deletions(-) diff --git a/api/lua/output.lua b/api/lua/output.lua index eacf64b..1e4fbe5 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -112,7 +112,7 @@ 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. +---Get the currently focused output. This is currently implemented as the one with the cursor on it. ---@return Output|nil output The output, or nil if none are focused. function output.get_focused() SendMsg({ diff --git a/src/api/msg.rs b/src/api/msg.rs index c2f11ec..69cb516 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -47,12 +47,10 @@ pub enum Msg { }, // Tag management - // FIXME: tag_id should not be a string ToggleTag { output_name: String, tag_name: String, }, - // FIXME: tag_id should not be a string SwitchToTag { output_name: String, tag_name: String, @@ -91,6 +89,7 @@ pub enum Msg { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct RequestId(pub u32); +#[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 { diff --git a/src/handlers.rs b/src/handlers.rs index c010080..11145bd 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -47,7 +47,6 @@ use smithay::{ use crate::{ backend::Backend, - layout::{Layout, LayoutVec}, state::{ClientState, State, WithState}, window::window_state::WindowResizeState, }; @@ -230,10 +229,7 @@ impl XdgShellHandler for State { self.space.outputs().next(), ) { (Some(output), _) | (None, Some(output)) => output.with_state(|state| { - let output_tags = state - .focused_tags() - .map(|tag| tag.clone()) - .collect::>(); + let output_tags = state.focused_tags().cloned().collect::>(); if !output_tags.is_empty() { output_tags } else if let Some(first_tag) = state.tags.first() { @@ -265,13 +261,15 @@ impl XdgShellHandler for State { self.loop_handle.insert_idle(move |data| { if let Some(focused_output) = &data.state.focus_state.focused_output { focused_output.with_state(|state| { - data.state - .windows - .to_master_stack( + let first_tag = state.focused_tags().next(); + if let Some(first_tag) = first_tag { + first_tag.layout().layout( + data.state.windows.clone(), + state.focused_tags().cloned().collect(), + &data.state.space, focused_output, - state.focused_tags().map(|tag| tag.clone()).collect(), - ) - .layout(&data.state.space, focused_output); + ); + } }); } }); @@ -282,12 +280,15 @@ impl XdgShellHandler for State { self.windows.retain(|window| window.toplevel() != &surface); if let Some(focused_output) = self.focus_state.focused_output.as_ref() { focused_output.with_state(|state| { - self.windows - .to_master_stack( + let first_tag = state.focused_tags().next(); + if let Some(first_tag) = first_tag { + first_tag.layout().layout( + self.windows.clone(), + state.focused_tags().cloned().collect(), + &self.space, focused_output, - state.focused_tags().map(|tag| tag.clone()).collect(), - ) - .layout(&self.space, focused_output); + ); + } }); } diff --git a/src/layout.rs b/src/layout.rs index 75904eb..9b1bfa4 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -5,7 +5,7 @@ // SPDX-License-Identifier: MPL-2.0 use smithay::{ - desktop::{space::SpaceElement, Space, Window}, + desktop::{Space, Window}, output::Output, utils::{Logical, Size}, }; @@ -24,127 +24,187 @@ pub enum Direction { Bottom, } -pub trait Layout { - fn layout(&self, space: &Space, output: &Output); +// TODO: couple this with the layouts +#[derive(Debug, Clone, Copy)] +pub enum Layout { + MasterStack, + Dwindle, } -pub struct MasterStack { - inner: Vec, - output: Output, -} +impl Layout { + pub fn layout( + &self, + windows: Vec, + tags: Vec, + space: &Space, + output: &Output, + ) { + let windows = filter_windows(&windows, tags); + match self { + Layout::MasterStack => { + let master = windows.first(); + let stack = windows.iter().skip(1); -impl MasterStack { - pub fn master(&self) -> Option<&Window> { - self.inner.first() - } + let Some(master) = master else { return }; - pub fn stack(&self) -> impl Iterator { - self.inner.iter().skip(1) - } + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; - fn layout_stack(&self, space: &Space, output: &Output) { - let stack_count = self.stack().count(); + let output_loc = output.current_location(); - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; + let stack_count = stack.clone().count(); - let output_loc = output.current_location(); + if stack_count == 0 { + // one window + master.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); - let height = output_geo.size.h / stack_count as i32; + master.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } else { + let new_master_size: Size = + (output_geo.size.w / 2, output_geo.size.h).into(); + master.toplevel().with_pending_state(|state| { + state.size = Some(new_master_size); + }); + master.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + master.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); - for (i, win) in self.stack().enumerate() { - win.toplevel().with_pending_state(|state| { - state.size = Some((output_geo.size.w / 2, height).into()); - }); + let stack_count = stack_count; - win.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( - win.toplevel().send_configure(), - ( - output_geo.size.w / 2 + output_loc.x, - i as i32 * height + output_loc.y, - ) - .into(), - ); - }); - } - } -} + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; -impl Layout for MasterStack { - fn layout(&self, space: &Space, output: &Output) { - let Some(master) = self.master() else { - return; - }; + let output_loc = output.current_location(); - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; + let height = output_geo.size.h / stack_count as i32; - let output_loc = output.current_location(); + for (i, win) in stack.enumerate() { + win.toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, height).into()); + }); - if self.stack().count() == 0 { - // one window - master.toplevel().with_pending_state(|state| { - state.size = Some(output_geo.size); - }); + win.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + win.toplevel().send_configure(), + ( + output_geo.size.w / 2 + output_loc.x, + i as i32 * height + output_loc.y, + ) + .into(), + ); + }); + } + } + } + Layout::Dwindle => { + let mut iter = windows.windows(2).peekable(); + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; - master.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (output_loc.x, output_loc.y).into(), - ); - }); - } else { - let new_master_size: Size = - (output_geo.size.w / 2, output_geo.size.h).into(); - master.toplevel().with_pending_state(|state| { - state.size = Some(new_master_size); - }); - master.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( - master.toplevel().send_configure(), - (output_loc.x, output_loc.y).into(), - ); - }); + let output_loc = output.current_location(); - self.layout_stack(space, output); - } - } -} + if iter.peek().is_none() { + if let Some(window) = windows.first() { + window.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); -pub struct Dwindle { - inner: Vec, - output: Output, -} + window.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + window.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } + } else { + let mut div_factor_w = 1; + let mut div_factor_h = 1; + let mut x_factor_1: f32; + let mut y_factor_1: f32; + let mut x_factor_2: f32 = 0.0; + let mut y_factor_2: f32 = 0.0; -impl Layout for Dwindle { - fn layout(&self, space: &Space, output: &Output) { - todo!() - } -} + for (i, wins) in iter.enumerate() { + let win1 = &wins[0]; + let win2 = &wins[1]; -pub trait LayoutVec { - /// Interpret this vec as a master-stack layout. - fn to_master_stack(&self, output: &Output, tags: Vec) -> MasterStack; - fn to_dwindle(&self, output: &Output, tags: Vec) -> Dwindle; -} + if i % 2 == 0 { + div_factor_w *= 2; + } else { + div_factor_h *= 2; + } -impl LayoutVec for Vec { - fn to_master_stack(&self, output: &Output, tags: Vec) -> MasterStack { - MasterStack { - inner: filter_windows(self, tags), - output: output.clone(), // TODO: get rid of? - } - } + win1.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); + win2.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); - fn to_dwindle(&self, output: &Output, tags: Vec) -> Dwindle { - Dwindle { - inner: filter_windows(self, tags), - output: output.clone(), + x_factor_1 = x_factor_2; + y_factor_1 = y_factor_2; + + if i % 2 == 0 { + x_factor_2 += (1.0 - x_factor_2) / 2.0; + } else { + y_factor_2 += (1.0 - y_factor_2) / 2.0; + } + + win1.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_1 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win1.toplevel().send_configure(), + new_loc, + ); + }); + win2.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_2 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_2 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win2.toplevel().send_configure(), + new_loc, + ); + }); + } + } + } } } } diff --git a/src/output.rs b/src/output.rs index 6d2e8bc..dcedfc7 100644 --- a/src/output.rs +++ b/src/output.rs @@ -38,7 +38,7 @@ impl WithState for Output { } impl OutputState { - pub fn focused_tags(&mut self) -> impl Iterator { - self.tags.iter_mut().filter(|tag| tag.active()) + pub fn focused_tags(&self) -> impl Iterator { + self.tags.iter().filter(|tag| tag.active()) } } diff --git a/src/state.rs b/src/state.rs index b1130b1..0627399 100644 --- a/src/state.rs +++ b/src/state.rs @@ -21,7 +21,6 @@ use crate::{ }, focus::FocusState, grab::resize_grab::ResizeSurfaceState, - layout::{Layout, LayoutVec}, tag::Tag, window::{window_state::WindowResizeState, WindowProperties}, }; @@ -187,7 +186,7 @@ impl State { .with_state(|op_state| { let tag = op_state.tags.iter().find(|tag| tag.name() == tag_id); if let Some(tag) = tag { - if state.tags.contains(&tag) { + if state.tags.contains(tag) { state.tags.retain(|tg| tg != tag); } else { state.tags.push(tag.clone()); @@ -638,15 +637,18 @@ impl State { 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::>(); + win.with_state(|state| state.tags.iter().any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output))) + }).cloned().collect::>(); let (render, do_not_render) = output.with_state(|state| { - self.windows - .to_master_stack( + let first_tag = state.focused_tags().next(); + if let Some(first_tag) = first_tag { + first_tag.layout().layout( + self.windows.clone(), + state.focused_tags().cloned().collect(), + &self.space, output, - state.focused_tags().map(|tag| tag.clone()).collect(), - ) - .layout(&self.space, output); + ); + } windows.iter().cloned().partition::, _>(|win| { win.with_state(|win_state| { diff --git a/src/tag.rs b/src/tag.rs index c418f0e..7769fcb 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -15,6 +15,7 @@ use smithay::output::Output; use crate::{ backend::Backend, + layout::Layout, state::{State, WithState}, }; @@ -37,7 +38,8 @@ struct TagInner { name: String, /// Whether this tag is active or not. active: bool, - // TODO: layout + /// What layout this tag has. + layout: Layout, } impl PartialEq for TagInner { @@ -67,6 +69,10 @@ impl Tag { pub fn set_active(&mut self, active: bool) { self.0.borrow_mut().active = active; } + + pub fn layout(&self) -> Layout { + self.0.borrow().layout + } } impl Tag { @@ -75,6 +81,7 @@ impl Tag { id: TagId::next(), name, active: false, + layout: Layout::Dwindle, // TODO: get from config }))) } } diff --git a/src/xdg/request.rs b/src/xdg/request.rs index ee2d61c..a7502f6 100644 --- a/src/xdg/request.rs +++ b/src/xdg/request.rs @@ -14,7 +14,7 @@ use smithay::{ use crate::{ backend::Backend, grab::{move_grab::MoveSurfaceGrab, resize_grab::ResizeSurfaceGrab}, - state::State, + state::{State, WithState}, }; pub fn move_request( @@ -94,6 +94,9 @@ pub fn resize_request( if let Some(start_data) = crate::pointer::pointer_grab_start_data(&pointer, wl_surface, serial) { let window = state.window_for_surface(wl_surface).unwrap(); + if window.with_state(|state| state.floating.is_tiled()) { + return; + } let initial_window_loc = state.space.element_location(&window).unwrap(); let initial_window_size = window.geometry().size; @@ -124,13 +127,16 @@ pub fn resize_request_force( edges: xdg_toplevel::ResizeEdge, button_used: u32, ) { - println!("resize_request_force started with edges {:?}", edges); let wl_surface = surface.wl_surface(); let pointer = seat.get_pointer().unwrap(); let window = state.window_for_surface(wl_surface).unwrap(); + if window.with_state(|state| state.floating.is_tiled()) { + return; + } + let initial_window_loc = state.space.element_location(&window).unwrap(); let initial_window_size = window.geometry().size; From f1508350e37f7d4d3e048ffa6e5936882c245bf3 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 12 Jul 2023 18:50:41 -0500 Subject: [PATCH 15/27] Add spiral layout --- api/lua/example_config.lua | 13 +++++ api/lua/msg.lua | 1 + api/lua/tag.lua | 31 +++++++++++ src/api/msg.rs | 10 +++- src/handlers.rs | 8 ++- src/layout.rs | 105 ++++++++++++++++++++++++++++++++++++- src/state.rs | 12 +++++ src/tag.rs | 8 ++- src/window.rs | 22 ++++++-- 9 files changed, 202 insertions(+), 8 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 08131b9..95d7321 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -65,6 +65,19 @@ require("pinnacle").setup(function(pinnacle) tag.toggle("1", op) end) + ---@type Layout[] + local layouts = { "MasterStack", "Dwindle", "Spiral" } + local index = 1 + + input.keybind({ mod_key }, keys.space, function() + tag.set_layout("1", layouts[index]) + if index + 1 > #layouts then + index = 1 + else + index = index + 1 + end + end) + input.keybind({ mod_key }, keys.KEY_1, function() tag.switch_to("1") end) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index b03b1a7..b889a3d 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -23,6 +23,7 @@ ---@field SwitchToTag { output_name: string, tag_name: string } ---@field AddTags { output_name: string, tags: string[] } ---@field RemoveTags { output_name: string, tags: string[] } +---@field SetLayout { output_name: string, tag_name: string, layout: Layout } --Outputs ---@field ConnectForAllOutputs { callback_id: integer } diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 9e648f8..ce62b1d 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -4,6 +4,11 @@ -- -- SPDX-License-Identifier: MPL-2.0 +---@alias Layout +---| "MasterStack" # One master window on the left with all other windows stacked to the right. +---| "Dwindle" # Windows split in half towards the bottom right corner. +---| "Spiral" # Windows split in half in a spiral. + local tag = {} ---Add tags. @@ -121,4 +126,30 @@ function tag.switch_to(name, output) end end +---Set a layout for the tag on the specified output. If there is none, set it for the tag on the currently focused one. +---@param name string The name of the tag. +---@param layout Layout The layout. +---@param output Output? The output. +function tag.set_layout(name, layout, output) + if output ~= nil then + SendMsg({ + SetLayout = { + output_name = output.name, + tag_name = name, + layout = layout, + }, + }) + else + local op = require("output").get_focused() + if op ~= nil then + SendMsg({ + SetLayout = { + output_name = op.name, + tag_name = name, + layout = layout, + }, + }) + end + end +end return tag diff --git a/src/api/msg.rs b/src/api/msg.rs index 69cb516..3c1b820 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -7,7 +7,10 @@ // The MessagePack format for these is a one-element map where the element's key is the enum name and its // value is a map of the enum's values -use crate::window::{window_state::WindowId, WindowProperties}; +use crate::{ + layout::Layout, + window::{window_state::WindowId, WindowProperties}, +}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); @@ -65,6 +68,11 @@ pub enum Msg { output_name: String, tags: Vec, }, + SetLayout { + output_name: String, + tag_name: String, + layout: Layout, + }, // Output management ConnectForAllOutputs { diff --git a/src/handlers.rs b/src/handlers.rs index 11145bd..660c96e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -19,7 +19,7 @@ use smithay::{ }, reexports::{ calloop::Interest, - wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, + wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, wayland_server::{ protocol::{wl_buffer::WlBuffer, wl_seat::WlSeat, wl_surface::WlSurface}, Client, Resource, @@ -223,6 +223,12 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new(surface); + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.set(xdg_toplevel::State::TiledTop); + tl_state.states.set(xdg_toplevel::State::TiledBottom); + tl_state.states.set(xdg_toplevel::State::TiledLeft); + tl_state.states.set(xdg_toplevel::State::TiledRight); + }); window.with_state(|state| { state.tags = match ( &self.focus_state.focused_output, diff --git a/src/layout.rs b/src/layout.rs index 9b1bfa4..8e594d5 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -25,10 +25,11 @@ pub enum Direction { } // TODO: couple this with the layouts -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum Layout { MasterStack, Dwindle, + Spiral, } impl Layout { @@ -176,6 +177,108 @@ impl Layout { y_factor_2 += (1.0 - y_factor_2) / 2.0; } + win1.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_1 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win1.toplevel().send_configure(), + new_loc, + ); + }); + win2.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_2 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_2 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win2.toplevel().send_configure(), + new_loc, + ); + }); + } + } + } + Layout::Spiral => { + let mut iter = windows.windows(2).peekable(); + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + let output_loc = output.current_location(); + + if iter.peek().is_none() { + if let Some(window) = windows.first() { + window.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + window.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + window.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } + } else { + let mut div_factor_w = 1; + let mut div_factor_h = 1; + let mut x_factor_1: f32 = 0.0; + let mut y_factor_1: f32; + let mut x_factor_2: f32 = 0.0; + let mut y_factor_2: f32; + + fn series(n: u32) -> f32 { + (0..n) + .map(|n| (-1i32).pow(n) as f32 * (1.0 / 2.0_f32.powi(n as i32))) + .sum() + } + + for (i, wins) in iter.enumerate() { + let win1 = &wins[0]; + let win2 = &wins[1]; + + if i % 2 == 0 { + div_factor_w *= 2; + } else { + div_factor_h *= 2; + } + + win1.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); + win2.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); + + y_factor_1 = x_factor_1; + y_factor_2 = x_factor_2; + + x_factor_1 = { + let first = (i / 4) * 2; + let indices = [first, first + 2, first + 3, first + 2]; + series(indices[i % 4] as u32) + }; + x_factor_2 = series((i as u32 / 4 + 1) * 2); + win1.with_state(|state| { let new_loc = ( (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) diff --git a/src/state.rs b/src/state.rs index 0627399..8405cff 100644 --- a/src/state.rs +++ b/src/state.rs @@ -271,6 +271,18 @@ impl State { }); } } + Msg::SetLayout { output_name, tag_name, layout } => { + 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) { + tag.set_layout(layout); + } + }); + self.re_layout(&output); + } + } Msg::ConnectForAllOutputs { callback_id } => { let stream = self diff --git a/src/tag.rs b/src/tag.rs index 7769fcb..9f866db 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -66,13 +66,17 @@ impl Tag { self.0.borrow().active } - pub fn set_active(&mut self, active: bool) { + pub fn set_active(&self, active: bool) { self.0.borrow_mut().active = active; } pub fn layout(&self) -> Layout { self.0.borrow().layout } + + pub fn set_layout(&self, layout: Layout) { + self.0.borrow_mut().layout = layout; + } } impl Tag { @@ -81,7 +85,7 @@ impl Tag { id: TagId::next(), name, active: false, - layout: Layout::Dwindle, // TODO: get from config + layout: Layout::MasterStack, // TODO: get from config }))) } } diff --git a/src/window.rs b/src/window.rs index 6dda1bc..6a47320 100644 --- a/src/window.rs +++ b/src/window.rs @@ -5,7 +5,11 @@ // SPDX-License-Identifier: MPL-2.0 use smithay::{ - desktop::Window, reexports::wayland_server::protocol::wl_surface::WlSurface, + desktop::Window, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, + wayland_server::protocol::wl_surface::WlSurface, + }, wayland::seat::WaylandFocus, }; @@ -46,10 +50,17 @@ pub fn toggle_floating(state: &mut State, window: &Window) { window.toplevel().send_pending_configure(); - state.space.map_element(window.clone(), prev_loc, false); // TODO: should it activate? + state.space.map_element(window.clone(), prev_loc, false); + // TODO: should it activate? } window_state.floating = Float::Floating; + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.unset(xdg_toplevel::State::TiledTop); + tl_state.states.unset(xdg_toplevel::State::TiledBottom); + tl_state.states.unset(xdg_toplevel::State::TiledLeft); + tl_state.states.unset(xdg_toplevel::State::TiledRight); + }); } Float::Floating => { window_state.floating = Float::Tiled(Some(( @@ -58,6 +69,12 @@ pub fn toggle_floating(state: &mut State, window: &Window) { state.space.element_location(window).unwrap(), window.geometry().size, ))); + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.set(xdg_toplevel::State::TiledTop); + tl_state.states.set(xdg_toplevel::State::TiledBottom); + tl_state.states.set(xdg_toplevel::State::TiledLeft); + tl_state.states.set(xdg_toplevel::State::TiledRight); + }); } } }); @@ -65,7 +82,6 @@ pub fn toggle_floating(state: &mut State, window: &Window) { 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| { state .windows From 91e9e77ff355797ae01c29ab156830a9acc04133 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 12 Jul 2023 18:56:00 -0500 Subject: [PATCH 16/27] Add workaround for crash when windows become too short --- src/layout.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 8e594d5..bcc59fb 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -91,7 +91,7 @@ impl Layout { let output_loc = output.current_location(); - let height = output_geo.size.h / stack_count as i32; + let height = i32::max(output_geo.size.h / stack_count as i32, 40); for (i, win) in stack.enumerate() { win.toplevel().with_pending_state(|state| { @@ -154,7 +154,7 @@ impl Layout { win1.toplevel().with_pending_state(|state| { let new_size = ( output_geo.size.w / div_factor_w, - output_geo.size.h / div_factor_h, + i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); state.size = Some(new_size); @@ -162,7 +162,7 @@ impl Layout { win2.toplevel().with_pending_state(|state| { let new_size = ( output_geo.size.w / div_factor_w, - output_geo.size.h / div_factor_h, + i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); state.size = Some(new_size); @@ -255,7 +255,7 @@ impl Layout { win1.toplevel().with_pending_state(|state| { let new_size = ( output_geo.size.w / div_factor_w, - output_geo.size.h / div_factor_h, + i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); state.size = Some(new_size); @@ -263,7 +263,7 @@ impl Layout { win2.toplevel().with_pending_state(|state| { let new_size = ( output_geo.size.w / div_factor_w, - output_geo.size.h / div_factor_h, + i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); state.size = Some(new_size); From 21272c4530e5dace923e96317148b0de1552311f Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 12 Jul 2023 21:48:47 -0500 Subject: [PATCH 17/27] Update README --- README.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a8e4975..665e6c5 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Cool stuff happens on the dev branch sometimes, check it out! - [x] Udev backend - This is currently just a copy of Anvil's udev backend. - [x] Basic tags - - Tags are currently very jank on the udev backend with multiple monitors. If you're checking udev out, I suggest unplugging all but one monitor or just using the winit backend until I flesh out the tag system. - [ ] Widget system - [ ] Layout system - [ ] Server-side decorations +- [ ] Animations and blur and all that pizazz - [ ] The other stuff Awesome has - [ ] XWayland support - [ ] Layer-shell support @@ -38,15 +38,18 @@ So, this is my attempt at making an Awesome-esque Wayland compositor. ## Dependencies You'll need the following packages, as specified by [Smithay](https://github.com/Smithay/smithay): -``` -libwayland -libxkbcommon -libudev -libinput -libgdm -libseat -``` -Package names will vary across distros. TODO: list those names. +`libwayland libxkbcommon libudev libinput libgdm libseat` +- Arch: + ``` + sudo pacman -S wayland libxkbcommon systemd-libs libinput libgdm seatd + ``` +- Debian: + ``` + sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgdm-dev libseat-dev + ``` +- TODO: other distros. + +You'll also need Lua 5.4 for configuration. ## Building Build the project with: @@ -72,7 +75,7 @@ cargo run [--release] -- -- - `udev`: run Pinnacle in a tty. NOTE: I tried running udev in Awesome and some things broke so uh, don't do that ## Configuration -Please note: this is VERY WIP and has basically no options yet. +Please note: this is VERY WIP and has few options. Pinnacle supports configuration through Lua (and hopefully more languages if it's not too unwieldy :crab:). From e7f0210f40a8504b47c78928fe15c30f9a5ff578 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 12 Jul 2023 22:10:53 -0500 Subject: [PATCH 18/27] Fix master stack layout having empty space on the bottom --- src/layout.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index bcc59fb..6ef4aa3 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -91,23 +91,44 @@ impl Layout { let output_loc = output.current_location(); + // INFO: Some windows crash the compositor if they become too short in height, + // | so they're limited to a minimum of 40 pixels as a workaround. let height = i32::max(output_geo.size.h / stack_count as i32, 40); + let mut empty_height_at_bottom = + output_geo.size.h - (height * stack_count as i32); + let mut heights = vec![height; stack_count]; + + // PERF: this cycles through the vec adding 1 pixel until all space is filled + if empty_height_at_bottom > 0 { + 'outer: loop { + for ht in heights.iter_mut() { + if empty_height_at_bottom == 0 { + break 'outer; + } + *ht += 1; + empty_height_at_bottom -= 1; + } + } + } + + let mut y = 0; + + tracing::debug!("heights: {heights:?}"); + for (i, win) in stack.enumerate() { win.toplevel().with_pending_state(|state| { - state.size = Some((output_geo.size.w / 2, height).into()); + state.size = Some((output_geo.size.w / 2, heights[i]).into()); }); win.with_state(|state| { state.resize_state = WindowResizeState::WaitingForAck( win.toplevel().send_configure(), - ( - output_geo.size.w / 2 + output_loc.x, - i as i32 * height + output_loc.y, - ) - .into(), + (output_geo.size.w / 2 + output_loc.x, y + output_loc.y).into(), ); }); + + y += heights[i]; } } } @@ -236,6 +257,7 @@ impl Layout { let mut x_factor_2: f32 = 0.0; let mut y_factor_2: f32; + // really starting to get flashbacks to calculus class here fn series(n: u32) -> f32 { (0..n) .map(|n| (-1i32).pow(n) as f32 * (1.0 / 2.0_f32.powi(n as i32))) From cc8ec304d4e9641e3a88354d3ae23e836a7d8ce4 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Thu, 13 Jul 2023 18:44:07 -0500 Subject: [PATCH 19/27] Add proper fix for windows not reappearing when unmapped and not sizing correctly when too small --- src/handlers.rs | 20 -------------------- src/layout.rs | 8 ++++---- src/state.rs | 7 ++++++- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index 660c96e..7f0cbf6 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -398,9 +398,7 @@ impl XdgShellHandler for State { } fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { - // tracing::debug!("start of ack_configure"); if let Some(window) = self.window_for_surface(&surface) { - // tracing::debug!("found window for surface"); window.with_state(|state| { if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state { match &configure { @@ -414,24 +412,6 @@ impl XdgShellHandler for State { } } }); - - // HACK: If a window is currently going through something that generates a bunch of - // | commits, like an animation, unmapping it while it's doing that has a chance - // | to cause any send_configures to not trigger a commit. I'm not sure if this is because of - // | the way I've implemented things or if it's something else. Because of me - // | mapping the element in commit, this means that the window won't reappear on a tag - // | change. The code below is a workaround until I can figure it out. - if !self.space.elements().any(|win| win == &window) { - window.with_state(|state| { - if let WindowResizeState::WaitingForCommit(new_loc) = state.resize_state { - tracing::debug!("remapping window"); - let win = window.clone(); - self.loop_handle.insert_idle(move |data| { - data.state.space.map_element(win, new_loc, false); - }); - } - }); - } } } diff --git a/src/layout.rs b/src/layout.rs index 6ef4aa3..ed66e4f 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -174,7 +174,7 @@ impl Layout { win1.toplevel().with_pending_state(|state| { let new_size = ( - output_geo.size.w / div_factor_w, + i32::max(output_geo.size.w / div_factor_w, 1), i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); @@ -182,7 +182,7 @@ impl Layout { }); win2.toplevel().with_pending_state(|state| { let new_size = ( - output_geo.size.w / div_factor_w, + i32::max(output_geo.size.w / div_factor_w, 1), i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); @@ -276,7 +276,7 @@ impl Layout { win1.toplevel().with_pending_state(|state| { let new_size = ( - output_geo.size.w / div_factor_w, + i32::max(output_geo.size.w / div_factor_w, 1), i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); @@ -284,7 +284,7 @@ impl Layout { }); win2.toplevel().with_pending_state(|state| { let new_size = ( - output_geo.size.w / div_factor_w, + i32::max(output_geo.size.w / div_factor_w, 1), i32::max(output_geo.size.h / div_factor_h, 40), ) .into(); diff --git a/src/state.rs b/src/state.rs index 8405cff..770f772 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,7 +11,7 @@ use std::{ os::{fd::AsRawFd, unix::net::UnixStream}, path::Path, process::Stdio, - sync::{Arc, Mutex}, + sync::{Arc, Mutex}, time::Duration, }; use crate::{ @@ -676,6 +676,11 @@ impl State { }) }) }); + for window in render.iter() { + // INFO: Here we send a frame with a duration of 0. This is because some windows won't + // | send a commit when they're not visible. More info in smithay::desktop::utils::send_frames_surface_tree + window.send_frame(output, self.clock.now(), Some(Duration::ZERO), surface_primary_scanout_output); + } self.schedule_on_commit(render, |data| { for win in do_not_render { data.state.space.unmap_elem(&win); From 0d8c30219cca22d14d451e9b217799ef427ecdae Mon Sep 17 00:00:00 2001 From: Ottatop Date: Thu, 13 Jul 2023 19:02:35 -0500 Subject: [PATCH 20/27] Rename enum members for clarity --- src/handlers.rs | 6 +++--- src/layout.rs | 22 +++++++++++----------- src/window/window_state.rs | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index 7f0cbf6..bc02abb 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -116,7 +116,7 @@ impl CompositorHandler for State { if let Some(window) = self.window_for_surface(surface) { window.with_state(|state| { - if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state { + if let WindowResizeState::Acknowledged(new_pos) = state.resize_state { state.resize_state = WindowResizeState::Idle; self.space.map_element(window.clone(), new_pos, false); } @@ -400,12 +400,12 @@ impl XdgShellHandler for State { fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { if let Some(window) = self.window_for_surface(&surface) { window.with_state(|state| { - if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state { + if let WindowResizeState::Requested(serial, new_loc) = state.resize_state { match &configure { Configure::Toplevel(configure) => { if configure.serial >= serial { tracing::debug!("acked configure, new loc is {:?}", new_loc); - state.resize_state = WindowResizeState::WaitingForCommit(new_loc); + state.resize_state = WindowResizeState::Acknowledged(new_loc); } } Configure::Popup(_) => todo!(), diff --git a/src/layout.rs b/src/layout.rs index ed66e4f..339b9fb 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -64,7 +64,7 @@ impl Layout { }); master.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( master.toplevel().send_configure(), (output_loc.x, output_loc.y).into(), ); @@ -76,7 +76,7 @@ impl Layout { state.size = Some(new_master_size); }); master.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( master.toplevel().send_configure(), (output_loc.x, output_loc.y).into(), ); @@ -122,7 +122,7 @@ impl Layout { }); win.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( win.toplevel().send_configure(), (output_geo.size.w / 2 + output_loc.x, y + output_loc.y).into(), ); @@ -148,7 +148,7 @@ impl Layout { }); window.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( window.toplevel().send_configure(), (output_loc.x, output_loc.y).into(), ); @@ -206,7 +206,7 @@ impl Layout { as i32, ) .into(); - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( win1.toplevel().send_configure(), new_loc, ); @@ -219,7 +219,7 @@ impl Layout { as i32, ) .into(); - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( win2.toplevel().send_configure(), new_loc, ); @@ -243,7 +243,7 @@ impl Layout { }); window.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( window.toplevel().send_configure(), (output_loc.x, output_loc.y).into(), ); @@ -309,7 +309,7 @@ impl Layout { as i32, ) .into(); - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( win1.toplevel().send_configure(), new_loc, ); @@ -322,7 +322,7 @@ impl Layout { as i32, ) .into(); - state.resize_state = WindowResizeState::WaitingForAck( + state.resize_state = WindowResizeState::Requested( win2.toplevel().send_configure(), new_loc, ); @@ -371,12 +371,12 @@ impl State { let serial = win1.toplevel().send_configure(); win1.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc); + state.resize_state = WindowResizeState::Requested(serial, win2_loc); }); let serial = win2.toplevel().send_configure(); win2.with_state(|state| { - state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc); + state.resize_state = WindowResizeState::Requested(serial, win1_loc); }); let mut elems = self diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 3bd9032..19df3da 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -70,12 +70,12 @@ pub enum WindowResizeState { Idle, /// The window has received a configure request with a new size. The desired location and the /// configure request's serial should be provided here. - WaitingForAck(Serial, Point), + Requested(Serial, Point), /// The client has received the configure request and has successfully changed its size. It's /// now safe to move the window in [`CompositorHandler.commit()`] without flickering. /// /// [`CompositorHandler.commit()`]: smithay::wayland::compositor::CompositorHandler#tymethod.commit - WaitingForCommit(Point), + Acknowledged(Point), } pub enum Float { From 8621d879388584eb91551374e7271ae7d4276a6f Mon Sep 17 00:00:00 2001 From: Ottatop Date: Thu, 13 Jul 2023 19:04:44 -0500 Subject: [PATCH 21/27] Update docs because I managed to not do that --- src/window/window_state.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 19df3da..8c4c4c5 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -46,20 +46,20 @@ pub struct WindowState { /// sending a configure event. However, the client will probably not acknowledge the configure /// until *after* the window has moved, causing flickering. /// -/// To solve this, we need to create two additional steps: [`WaitingForAck`] and [`WaitingForCommit`]. +/// To solve this, we need to create two additional steps: [`Requested`] and [`Acknowledged`]. /// If we need to change a window's location when we change its size, instead of /// calling `map_element()`, we change the window's [`WindowState`] and set -/// its [`resize_state`] to `WaitingForAck` with the new position we want. +/// its [`resize_state`] to `Requested` with the new position we want. /// -/// When the client acks the configure, we can move the state to `WaitingForCommit` in +/// When the client acks the configure, we can move the state to `Acknowledged` in /// [`XdgShellHandler.ack_configure()`]. Finally, in [`CompositorHandler.commit()`], we set the /// state back to [`Idle`] and map the window. /// /// [`space.map_element()`]: smithay::desktop::space::Space#method.map_element /// [`with_pending_state()`]: smithay::wayland::shell::xdg::ToplevelSurface#method.with_pending_state /// [`Idle`]: WindowResizeState::Idle -/// [`WaitingForAck`]: WindowResizeState::WaitingForAck -/// [`WaitingForCommit`]: WindowResizeState::WaitingForCommit +/// [`Requested`]: WindowResizeState::Requested +/// [`Acknowledged`]: WindowResizeState::Acknowledged /// [`resize_state`]: WindowState#structfield.resize_state /// [`XdgShellHandler.ack_configure()`]: smithay::wayland::shell::xdg::XdgShellHandler#method.ack_configure /// [`CompositorHandler.commit()`]: smithay::wayland::compositor::CompositorHandler#tymethod.commit From 15d5778daba0136b131caeb6aad015340d063e36 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sun, 16 Jul 2023 20:44:18 -0500 Subject: [PATCH 22/27] Fix windows not resizing when spawning very quickly --- src/api/msg.rs | 2 +- src/handlers.rs | 101 +++++++++++++++++++++++++++++-------- src/state.rs | 83 +++++++++++++++++------------- src/window.rs | 26 ++++++++-- src/window/window_state.rs | 13 ++++- 5 files changed, 165 insertions(+), 60 deletions(-) diff --git a/src/api/msg.rs b/src/api/msg.rs index 3c1b820..e4930c8 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -119,7 +119,7 @@ pub enum Modifier { Super = 0b0000_1000, } -/// A bitmask of [Modifiers] for the purpose of hashing. +/// A bitmask of [`Modifier`]s for the purpose of hashing. #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub struct ModifierMask(u8); diff --git a/src/handlers.rs b/src/handlers.rs index bc02abb..178bee5 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -4,14 +4,16 @@ // // SPDX-License-Identifier: MPL-2.0 +use std::time::Duration; + use smithay::{ backend::renderer::utils, delegate_compositor, delegate_data_device, delegate_fractional_scale, delegate_output, delegate_presentation, delegate_relative_pointer, delegate_seat, delegate_shm, delegate_viewporter, delegate_xdg_shell, desktop::{ - find_popup_root_surface, PopupKeyboardGrab, PopupKind, PopupPointerGrab, - PopupUngrabStrategy, Window, + find_popup_root_surface, utils::surface_primary_scanout_output, PopupKeyboardGrab, + PopupKind, PopupPointerGrab, PopupUngrabStrategy, Window, }, input::{ pointer::{CursorImageStatus, Focus}, @@ -48,7 +50,7 @@ use smithay::{ use crate::{ backend::Backend, state::{ClientState, State, WithState}, - window::window_state::WindowResizeState, + window::{window_state::WindowResizeState, WindowBlocker, BLOCKER_COUNTER}, }; impl BufferHandler for State { @@ -122,6 +124,12 @@ impl CompositorHandler for State { } }); } + // let states = self + // .windows + // .iter() + // .map(|win| win.with_state(|state| state.resize_state.clone())) + // .collect::>(); + // tracing::debug!("states: {states:?}"); } fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { @@ -250,8 +258,67 @@ impl XdgShellHandler for State { tracing::debug!("new window, tags are {:?}", state.tags); }); + let windows_on_output = self + .windows + .iter() + .filter(|win| { + win.with_state(|state| { + self.focus_state + .focused_output + .as_ref() + .unwrap() + .with_state(|op_state| { + op_state + .tags + .iter() + .any(|tag| state.tags.iter().any(|tg| tg == tag)) + }) + }) + }) + .cloned() + .collect::>(); + self.windows.push(window.clone()); // self.space.map_element(window.clone(), (0, 0), true); + if let Some(focused_output) = self.focus_state.focused_output.clone() { + focused_output.with_state(|state| { + let first_tag = state.focused_tags().next(); + if let Some(first_tag) = first_tag { + first_tag.layout().layout( + self.windows.clone(), + state.focused_tags().cloned().collect(), + &self.space, + &focused_output, + ); + } + }); + BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst); + tracing::debug!( + "blocker {}", + BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) + ); + for win in windows_on_output.iter() { + compositor::add_blocker(win.toplevel().wl_surface(), WindowBlocker); + } + let clone = window.clone(); + self.loop_handle.insert_idle(|data| { + crate::state::schedule_on_commit(data, vec![clone], move |data| { + BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst); + tracing::debug!( + "blocker {}", + BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) + ); + for client in windows_on_output + .iter() + .filter_map(|win| win.toplevel().wl_surface().client()) + { + data.state + .client_compositor_state(&client) + .blocker_cleared(&mut data.state, &data.display.handle()) + } + }) + }); + } self.loop_handle.insert_idle(move |data| { data.state .seat @@ -263,22 +330,6 @@ impl XdgShellHandler for State { SERIAL_COUNTER.next_serial(), ); }); - - self.loop_handle.insert_idle(move |data| { - if let Some(focused_output) = &data.state.focus_state.focused_output { - focused_output.with_state(|state| { - let first_tag = state.focused_tags().next(); - if let Some(first_tag) = first_tag { - first_tag.layout().layout( - data.state.windows.clone(), - state.focused_tags().cloned().collect(), - &data.state.space, - focused_output, - ); - } - }); - } - }); } fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { @@ -404,8 +455,18 @@ impl XdgShellHandler for State { match &configure { Configure::Toplevel(configure) => { if configure.serial >= serial { - tracing::debug!("acked configure, new loc is {:?}", new_loc); + // tracing::debug!("acked configure, new loc is {:?}", new_loc); state.resize_state = WindowResizeState::Acknowledged(new_loc); + if let Some(focused_output) = + self.focus_state.focused_output.clone() + { + window.send_frame( + &focused_output, + self.clock.now(), + Some(Duration::ZERO), + surface_primary_scanout_output, + ); + } } } Configure::Popup(_) => todo!(), diff --git a/src/state.rs b/src/state.rs index 770f772..25d4b8e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,7 +11,7 @@ use std::{ os::{fd::AsRawFd, unix::net::UnixStream}, path::Path, process::Stdio, - sync::{Arc, Mutex}, time::Duration, + sync::{Arc, Mutex}, }; use crate::{ @@ -676,46 +676,59 @@ impl State { }) }) }); - for window in render.iter() { - // INFO: Here we send a frame with a duration of 0. This is because some windows won't - // | send a commit when they're not visible. More info in smithay::desktop::utils::send_frames_surface_tree - window.send_frame(output, self.clock.now(), Some(Duration::ZERO), surface_primary_scanout_output); - } - self.schedule_on_commit(render, |data| { - for win in do_not_render { - data.state.space.unmap_elem(&win); - } - }); - } - /// Schedule something to be done when windows have finished committing and have become - /// idle. - pub fn schedule_on_commit(&mut self, windows: Vec, on_commit: F) - where - F: FnOnce(&mut CalloopData) + 'static, - { - tracing::debug!("scheduling on_commit"); + let clone = render.clone(); self.loop_handle.insert_idle(|data| { - tracing::debug!("running idle cb"); - tracing::debug!("win len is {}", windows.len()); - for window in windows.iter() { - window.with_state(|state| { - tracing::debug!("win state is {:?}", state.resize_state); - }); - if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) - { - tracing::debug!("some windows not idle"); - data.state.loop_handle.insert_idle(|data| { - data.state.schedule_on_commit(windows, on_commit); - }); - return; + schedule_on_commit(data, clone, |dt| { + for win in do_not_render { + dt.state.space.unmap_elem(&win); } - } - - on_commit(data); + }) }); + + // let blocker = WindowBlockerAll::new(render.clone()); + // blocker.insert_into_loop(self); + // for win in render.iter() { + // compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone()); + // } + + // let (blocker, source) = WindowBlocker::block_all::(render.clone()); + // for win in render.iter() { + // compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone()); + // } + // self.loop_handle.insert_idle(move |data| source(render.clone(), data)); + + // let (blocker, source) = WindowBlocker::new::(render.clone()); + // for win in render.iter() { + // compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone()); + // } + // self.loop_handle.insert_idle(move |data| source(render.clone(), render.clone(), data)); } } +/// Schedule something to be done when windows have finished committing and have become +/// idle. +pub fn schedule_on_commit(data: &mut CalloopData, windows: Vec, on_commit: F) + where + F: FnOnce(&mut CalloopData) + 'static, +{ + // tracing::debug!("scheduling on_commit"); + // tracing::debug!("win len is {}", windows.len()); + for window in windows.iter() { + window.with_state(|state| { + // tracing::debug!("win state is {:?}", state.resize_state); + }); + if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) + { + // tracing::debug!("some windows not idle"); + data.state.loop_handle.insert_idle(|data| { + schedule_on_commit(data, windows, on_commit); + }); + return; + } + } + + on_commit(data); +} impl State { pub fn init( diff --git a/src/window.rs b/src/window.rs index 6a47320..1571921 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,13 +4,18 @@ // // SPDX-License-Identifier: MPL-2.0 +use std::sync::atomic::AtomicU32; + use smithay::{ desktop::Window, reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface, }, - wayland::seat::WaylandFocus, + wayland::{ + compositor::{Blocker, BlockerState}, + seat::WaylandFocus, + }, }; use crate::{ @@ -104,8 +109,10 @@ pub fn toggle_floating(state: &mut State, window: &Window) { }); let clone = window.clone(); - state.schedule_on_commit(render, move |data| { - data.state.space.raise_element(&clone, true); + state.loop_handle.insert_idle(move |data| { + crate::state::schedule_on_commit(data, render, move |dt| { + dt.state.space.raise_element(&clone, true); + }); }); } @@ -120,3 +127,16 @@ pub struct WindowProperties { pub location: (i32, i32), pub floating: bool, } + +pub struct WindowBlocker; +pub static BLOCKER_COUNTER: AtomicU32 = AtomicU32::new(0); + +impl Blocker for WindowBlocker { + fn state(&self) -> BlockerState { + if BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) > 0 { + BlockerState::Pending + } else { + BlockerState::Released + } + } +} diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 8c4c4c5..50c50b5 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -6,6 +6,7 @@ use std::{ cell::RefCell, + fmt, sync::atomic::{AtomicU32, Ordering}, }; @@ -63,7 +64,7 @@ pub struct WindowState { /// [`resize_state`]: WindowState#structfield.resize_state /// [`XdgShellHandler.ack_configure()`]: smithay::wayland::shell::xdg::XdgShellHandler#method.ack_configure /// [`CompositorHandler.commit()`]: smithay::wayland::compositor::CompositorHandler#tymethod.commit -#[derive(Debug, Default)] +#[derive(Default, Clone)] pub enum WindowResizeState { /// The window doesn't need to be moved. #[default] @@ -78,6 +79,16 @@ pub enum WindowResizeState { Acknowledged(Point), } +impl fmt::Debug for WindowResizeState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Idle => write!(f, "Idle"), + Self::Requested(_arg0, _arg1) => write!(f, "Requested"), + Self::Acknowledged(_arg0) => write!(f, "Acknowledged"), + } + } +} + pub enum Float { /// The previous location and size of the window when it was floating, if any. Tiled(Option<(Point, Size)>), From e6eb0c67ff1052194326d81cd4409e851e45bee9 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Mon, 17 Jul 2023 18:48:01 -0500 Subject: [PATCH 23/27] Fix rounding errors when sizing windows --- src/layout.rs | 464 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 279 insertions(+), 185 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 339b9fb..c9f2718 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: MPL-2.0 +use itertools::{Either, Itertools}; use smithay::{ desktop::{Space, Window}, output::Output, @@ -17,19 +18,16 @@ use crate::{ window::window_state::WindowResizeState, }; -pub enum Direction { - Left, - Right, - Top, - Bottom, -} - // TODO: couple this with the layouts #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum Layout { MasterStack, Dwindle, Spiral, + CornerTopLeft, + CornerTopRight, + CornerBottomLeft, + CornerBottomRight, } impl Layout { @@ -41,6 +39,14 @@ impl Layout { output: &Output, ) { let windows = filter_windows(&windows, tags); + + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + let output_loc = output.current_location(); + match self { Layout::MasterStack => { let master = windows.first(); @@ -48,13 +54,6 @@ impl Layout { let Some(master) = master else { return }; - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - let output_loc = output.current_location(); - let stack_count = stack.clone().count(); if stack_count == 0 { @@ -84,62 +83,37 @@ impl Layout { let stack_count = stack_count; - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - let output_loc = output.current_location(); - - // INFO: Some windows crash the compositor if they become too short in height, - // | so they're limited to a minimum of 40 pixels as a workaround. - let height = i32::max(output_geo.size.h / stack_count as i32, 40); - - let mut empty_height_at_bottom = - output_geo.size.h - (height * stack_count as i32); - let mut heights = vec![height; stack_count]; - - // PERF: this cycles through the vec adding 1 pixel until all space is filled - if empty_height_at_bottom > 0 { - 'outer: loop { - for ht in heights.iter_mut() { - if empty_height_at_bottom == 0 { - break 'outer; - } - *ht += 1; - empty_height_at_bottom -= 1; - } - } + let height = output_geo.size.h as f32 / stack_count as f32; + let mut y_s = vec![]; + for i in 0..stack_count { + y_s.push((i as f32 * height).round() as i32); } - - let mut y = 0; - - tracing::debug!("heights: {heights:?}"); + let heights = y_s + .windows(2) + .map(|pair| pair[1] - pair[0]) + .chain(vec![output_geo.size.h - y_s.last().expect("vec was empty")]) + .collect::>(); for (i, win) in stack.enumerate() { win.toplevel().with_pending_state(|state| { - state.size = Some((output_geo.size.w / 2, heights[i]).into()); + // INFO: Some windows crash the compositor if they become too short in height, + // | so they're limited to a minimum of 40 pixels as a workaround. + state.size = + Some((output_geo.size.w / 2, i32::max(heights[i], 40)).into()); }); win.with_state(|state| { state.resize_state = WindowResizeState::Requested( win.toplevel().send_configure(), - (output_geo.size.w / 2 + output_loc.x, y + output_loc.y).into(), + (output_geo.size.w / 2 + output_loc.x, y_s[i] + output_loc.y) + .into(), ); }); - - y += heights[i]; } } } Layout::Dwindle => { let mut iter = windows.windows(2).peekable(); - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - let output_loc = output.current_location(); if iter.peek().is_none() { if let Some(window) = windows.first() { @@ -155,86 +129,93 @@ impl Layout { }); } } else { - let mut div_factor_w = 1; - let mut div_factor_h = 1; - let mut x_factor_1: f32; - let mut y_factor_1: f32; - let mut x_factor_2: f32 = 0.0; - let mut y_factor_2: f32 = 0.0; - for (i, wins) in iter.enumerate() { let win1 = &wins[0]; let win2 = &wins[1]; - if i % 2 == 0 { - div_factor_w *= 2; - } else { - div_factor_h *= 2; + enum Slice { + Right, + Below, } - win1.toplevel().with_pending_state(|state| { - let new_size = ( - i32::max(output_geo.size.w / div_factor_w, 1), - i32::max(output_geo.size.h / div_factor_h, 40), - ) - .into(); - state.size = Some(new_size); - }); - win2.toplevel().with_pending_state(|state| { - let new_size = ( - i32::max(output_geo.size.w / div_factor_w, 1), - i32::max(output_geo.size.h / div_factor_h, 40), - ) - .into(); - state.size = Some(new_size); - }); - - x_factor_1 = x_factor_2; - y_factor_1 = y_factor_2; - - if i % 2 == 0 { - x_factor_2 += (1.0 - x_factor_2) / 2.0; + let slice = if i % 2 == 0 { + Slice::Right } else { - y_factor_2 += (1.0 - y_factor_2) / 2.0; + Slice::Below + }; + + if i == 0 { + win1.toplevel() + .with_pending_state(|state| state.size = Some(output_geo.size)); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + output_loc, + ) + }); } - win1.with_state(|state| { - let new_loc = ( - (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) - as i32, - (output_geo.size.h as f32 * y_factor_1 + output_loc.y as f32) - as i32, - ) - .into(); - state.resize_state = WindowResizeState::Requested( - win1.toplevel().send_configure(), - new_loc, - ); + let win1_size = win1.toplevel().with_pending_state(|state| { + state.size.expect("size should have been set") }); - win2.with_state(|state| { - let new_loc = ( - (output_geo.size.w as f32 * x_factor_2 + output_loc.x as f32) - as i32, - (output_geo.size.h as f32 * y_factor_2 + output_loc.y as f32) - as i32, - ) - .into(); - state.resize_state = WindowResizeState::Requested( - win2.toplevel().send_configure(), - new_loc, - ); + let win1_loc = win1.with_state(|state| { + let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() }; + loc }); + + match slice { + Slice::Right => { + let width_partition = win1_size.w / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w - width_partition, win1_size.h).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + win1_loc, + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((width_partition, win1_size.h).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + (win1_loc.x + (win1_size.w - width_partition), win1_loc.y) + .into(), + ); + }); + } + Slice::Below => { + let height_partition = win1_size.h / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w, win1_size.h - height_partition).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + win1_loc, + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((win1_size.w, height_partition).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)) + .into(), + ); + }); + } + } } } } Layout::Spiral => { let mut iter = windows.windows(2).peekable(); - let Some(output_geo) = space.output_geometry(output) else { - tracing::error!("could not get output geometry"); - return; - }; - - let output_loc = output.current_location(); if iter.peek().is_none() { if let Some(window) = windows.first() { @@ -250,86 +231,199 @@ impl Layout { }); } } else { - let mut div_factor_w = 1; - let mut div_factor_h = 1; - let mut x_factor_1: f32 = 0.0; - let mut y_factor_1: f32; - let mut x_factor_2: f32 = 0.0; - let mut y_factor_2: f32; - - // really starting to get flashbacks to calculus class here - fn series(n: u32) -> f32 { - (0..n) - .map(|n| (-1i32).pow(n) as f32 * (1.0 / 2.0_f32.powi(n as i32))) - .sum() - } - for (i, wins) in iter.enumerate() { let win1 = &wins[0]; let win2 = &wins[1]; - if i % 2 == 0 { - div_factor_w *= 2; - } else { - div_factor_h *= 2; + enum Slice { + Above, + Below, + Left, + Right, } - win1.toplevel().with_pending_state(|state| { - let new_size = ( - i32::max(output_geo.size.w / div_factor_w, 1), - i32::max(output_geo.size.h / div_factor_h, 40), - ) - .into(); - state.size = Some(new_size); - }); - win2.toplevel().with_pending_state(|state| { - let new_size = ( - i32::max(output_geo.size.w / div_factor_w, 1), - i32::max(output_geo.size.h / div_factor_h, 40), - ) - .into(); - state.size = Some(new_size); - }); - - y_factor_1 = x_factor_1; - y_factor_2 = x_factor_2; - - x_factor_1 = { - let first = (i / 4) * 2; - let indices = [first, first + 2, first + 3, first + 2]; - series(indices[i % 4] as u32) + let slice = match i % 4 { + 0 => Slice::Right, + 1 => Slice::Below, + 2 => Slice::Left, + 3 => Slice::Above, + _ => unreachable!(), }; - x_factor_2 = series((i as u32 / 4 + 1) * 2); - win1.with_state(|state| { - let new_loc = ( - (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) - as i32, - (output_geo.size.h as f32 * y_factor_1 + output_loc.y as f32) - as i32, - ) - .into(); - state.resize_state = WindowResizeState::Requested( - win1.toplevel().send_configure(), - new_loc, - ); + if i == 0 { + win1.toplevel() + .with_pending_state(|state| state.size = Some(output_geo.size)); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + output_loc, + ) + }); + } + + let win1_size = win1.toplevel().with_pending_state(|state| { + state.size.expect("size should have been set") }); - win2.with_state(|state| { - let new_loc = ( - (output_geo.size.w as f32 * x_factor_2 + output_loc.x as f32) - as i32, - (output_geo.size.h as f32 * y_factor_2 + output_loc.y as f32) - as i32, - ) - .into(); - state.resize_state = WindowResizeState::Requested( - win2.toplevel().send_configure(), - new_loc, - ); + let win1_loc = win1.with_state(|state| { + let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() }; + loc }); + + match slice { + Slice::Above => { + let height_partition = win1_size.h / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w, win1_size.h - height_partition).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + (win1_loc.x, win1_loc.y + height_partition).into(), + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((win1_size.w, height_partition).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + win1_loc, + ); + }); + } + Slice::Below => { + let height_partition = win1_size.h / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w, win1_size.h - height_partition).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + win1_loc, + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((win1_size.w, height_partition).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)) + .into(), + ); + }); + } + Slice::Left => { + let width_partition = win1_size.w / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w - width_partition, win1_size.h).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + (win1_loc.x + width_partition, win1_loc.y).into(), + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((width_partition, win1_size.h).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + win1_loc, + ); + }); + } + Slice::Right => { + let width_partition = win1_size.w / 2; + win1.toplevel().with_pending_state(|state| { + state.size = + Some((win1_size.w - width_partition, win1_size.h).into()); + }); + win1.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win1.toplevel().send_configure(), + win1_loc, + ); + }); + win2.toplevel().with_pending_state(|state| { + state.size = Some((width_partition, win1_size.h).into()); + }); + win2.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win2.toplevel().send_configure(), + (win1_loc.x + (win1_size.w - width_partition), win1_loc.y) + .into(), + ); + }); + } + } } } } + Layout::CornerTopLeft => match windows.len() { + 0 => (), + 1 => { + windows[0].toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + windows[0].with_state(|state| { + state.resize_state = WindowResizeState::Requested( + windows[0].toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } + 2 => { + windows[0].toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, output_geo.size.h).into()); + }); + windows[0].with_state(|state| { + state.resize_state = WindowResizeState::Requested( + windows[0].toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + windows[1].toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, output_geo.size.h).into()); + }); + windows[1].with_state(|state| { + state.resize_state = WindowResizeState::Requested( + windows[1].toplevel().send_configure(), + (output_loc.x + output_geo.size.w / 2, output_loc.y).into(), + ); + }); + } + _ => { + let mut windows = windows.into_iter(); + let Some(corner) = windows.next() else { unreachable!() }; + let (horiz_stack, vert_stack): (Vec, Vec) = + windows.enumerate().partition_map(|(i, win)| { + if i % 2 == 0 { + Either::Left(win) + } else { + Either::Right(win) + } + }); + + corner.toplevel().with_pending_state(|state| { + state.size = Some((output_geo.size.w / 2, output_geo.size.h / 2).into()); + }); + corner.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + corner.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } + }, + Layout::CornerTopRight => todo!(), + Layout::CornerBottomLeft => todo!(), + Layout::CornerBottomRight => todo!(), } } } From f6784da8a8f9b68a79c973453c4ce854a7860b7d Mon Sep 17 00:00:00 2001 From: Ottatop Date: Mon, 17 Jul 2023 20:40:56 -0500 Subject: [PATCH 24/27] Add corner layout --- api/lua/example_config.lua | 10 ++- api/lua/tag.lua | 4 ++ src/layout.rs | 130 +++++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 7 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 95d7321..2811e46 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -66,7 +66,15 @@ require("pinnacle").setup(function(pinnacle) end) ---@type Layout[] - local layouts = { "MasterStack", "Dwindle", "Spiral" } + local layouts = { + "MasterStack", + "Dwindle", + "Spiral", + "CornerTopLeft", + "CornerTopRight", + "CornerBottomLeft", + "CornerBottomRight", + } local index = 1 input.keybind({ mod_key }, keys.space, function() diff --git a/api/lua/tag.lua b/api/lua/tag.lua index ce62b1d..326990c 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -8,6 +8,10 @@ ---| "MasterStack" # One master window on the left with all other windows stacked to the right. ---| "Dwindle" # Windows split in half towards the bottom right corner. ---| "Spiral" # Windows split in half in a spiral. +---| "CornerTopLeft" # One main corner window in the top left with a column of windows on the right and a row on the bottom. +---| "CornerTopRight" # One main corner window in the top right with a column of windows on the left and a row on the bottom. +---| "CornerBottomLeft" # One main corner window in the bottom left with a column of windows on the right and a row on the top. +---| "CornerBottomRight" # One main corner window in the bottom right with a column of windows on the left and a row on the top. local tag = {} diff --git a/src/layout.rs b/src/layout.rs index c9f2718..78c83a1 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -364,7 +364,10 @@ impl Layout { } } } - Layout::CornerTopLeft => match windows.len() { + layout @ (Layout::CornerTopLeft + | Layout::CornerTopRight + | Layout::CornerBottomLeft + | Layout::CornerBottomRight) => match windows.len() { 0 => (), 1 => { windows[0].toplevel().with_pending_state(|state| { @@ -410,20 +413,135 @@ impl Layout { } }); + let div_factor = 2; + corner.toplevel().with_pending_state(|state| { - state.size = Some((output_geo.size.w / 2, output_geo.size.h / 2).into()); + state.size = Some( + ( + output_geo.size.w / div_factor, + output_geo.size.h / div_factor, + ) + .into(), + ); }); corner.with_state(|state| { state.resize_state = WindowResizeState::Requested( corner.toplevel().send_configure(), - (output_loc.x, output_loc.y).into(), + match layout { + Layout::CornerTopLeft => (output_loc.x, output_loc.y), + Layout::CornerTopRight => ( + output_loc.x + output_geo.size.w + - output_geo.size.w / div_factor, + output_loc.y, + ), + Layout::CornerBottomLeft => ( + output_loc.x, + output_loc.y + output_geo.size.h + - output_geo.size.h / div_factor, + ), + Layout::CornerBottomRight => ( + output_loc.x + output_geo.size.w + - output_geo.size.w / div_factor, + output_loc.y + output_geo.size.h + - output_geo.size.h / div_factor, + ), + _ => unreachable!(), + } + .into(), ); }); + + let vert_stack_count = vert_stack.len(); + + let height = output_geo.size.h as f32 / vert_stack_count as f32; + let mut y_s = vec![]; + for i in 0..vert_stack_count { + y_s.push((i as f32 * height).round() as i32); + } + let heights = y_s + .windows(2) + .map(|pair| pair[1] - pair[0]) + .chain(vec![output_geo.size.h - y_s.last().expect("vec was empty")]) + .collect::>(); + + for (i, win) in vert_stack.iter().enumerate() { + win.toplevel().with_pending_state(|state| { + // INFO: Some windows crash the compositor if they become too short in height, + // | so they're limited to a minimum of 40 pixels as a workaround. + state.size = + Some((output_geo.size.w / 2, i32::max(heights[i], 40)).into()); + }); + + win.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win.toplevel().send_configure(), + ( + match layout { + Layout::CornerTopLeft | Layout::CornerBottomLeft => { + output_geo.size.w / 2 + output_loc.x + } + Layout::CornerTopRight | Layout::CornerBottomRight => { + output_loc.x + } + _ => unreachable!(), + }, + y_s[i] + output_loc.y, + ) + .into(), + ); + }); + } + + let horiz_stack_count = horiz_stack.len(); + + let width = output_geo.size.w as f32 / 2.0 / horiz_stack_count as f32; + let mut x_s = vec![]; + for i in 0..horiz_stack_count { + x_s.push((i as f32 * width).round() as i32); + } + let widths = x_s + .windows(2) + .map(|pair| pair[1] - pair[0]) + .chain(vec![ + output_geo.size.w / 2 - x_s.last().expect("vec was empty"), + ]) + .collect::>(); + + for (i, win) in horiz_stack.iter().enumerate() { + win.toplevel().with_pending_state(|state| { + // INFO: Some windows crash the compositor if they become too short in height, + // | so they're limited to a minimum of 40 pixels as a workaround. + state.size = + Some((i32::max(widths[i], 1), output_geo.size.h / 2).into()); + }); + + win.with_state(|state| { + state.resize_state = WindowResizeState::Requested( + win.toplevel().send_configure(), + match layout { + Layout::CornerTopLeft => ( + x_s[i] + output_loc.x, + output_loc.y + output_geo.size.h / 2, + ), + Layout::CornerTopRight => ( + x_s[i] + output_loc.x + output_geo.size.w / 2, + output_loc.y + output_geo.size.h / 2, + ), + Layout::CornerBottomLeft => { + (x_s[i] + output_loc.x, output_loc.y) + } + Layout::CornerBottomRight => ( + x_s[i] + output_loc.x + output_geo.size.w / 2, + output_loc.y, + ), + _ => unreachable!(), + } + .into(), + ); + }); + } } }, - Layout::CornerTopRight => todo!(), - Layout::CornerBottomLeft => todo!(), - Layout::CornerBottomRight => todo!(), } } } From b1ee8e03c168d75cf56429a1d9ea1d1b4c0a1aca Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 18 Jul 2023 10:31:08 -0500 Subject: [PATCH 25/27] Add more stuff to API --- api/lua/example_config.lua | 18 ++- api/lua/msg.lua | 5 + api/lua/output.lua | 38 ++++- api/lua/tag.lua | 55 ++++++- src/api/msg.rs | 3 + src/state.rs | 317 ++++++++++++++++++------------------- src/tag.rs | 5 + 7 files changed, 269 insertions(+), 172 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 2811e46..cf858cf 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -51,17 +51,11 @@ require("pinnacle").setup(function(pinnacle) process.spawn("nautilus") end) - input.keybind({ mod_key }, keys.g, function() - local op = output.get_by_res(2560, 1440) - for _, v in pairs(op) do - print(v.name) - end - end) - -- Tags --------------------------------------------------------------------------- output.connect_for_all(function(op) - tag.add(op, "1", "2", "3", "4", "5") + op:add_tags("1", "2", "3", "4", "5") + -- Same as tag.add(op, "1", "2", "3", "4", "5") tag.toggle("1", op) end) @@ -85,6 +79,14 @@ require("pinnacle").setup(function(pinnacle) index = index + 1 end end) + input.keybind({ mod_key, "Shift" }, keys.space, function() + tag.set_layout("1", layouts[index]) + if index - 1 < 1 then + index = #layouts + else + index = index - 1 + end + end) input.keybind({ mod_key }, keys.KEY_1, function() tag.switch_to("1") diff --git a/api/lua/msg.lua b/api/lua/msg.lua index b889a3d..1eb04fe 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -39,6 +39,7 @@ ---@field GetOutputByName { name: string } ---@field GetOutputsByModel { model: string } ---@field GetOutputsByRes { res: integer[] } +---@field GetTagsByOutput { output: string } ---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus" @@ -54,6 +55,7 @@ ---@field Window { window: WindowProperties } ---@field GetAllWindows { windows: WindowProperties[] } ---@field Outputs { names: string[] } +---@field Tags { tags: TagProperties[] } ---@class WindowProperties ---@field id integer @@ -62,3 +64,6 @@ ---@field size integer[] A two element int array, \[1\] = w, \[2\] = h ---@field location integer[] A two element int array, \[1\] = x, \[2\] = y ---@field floating boolean + +---@class TagProperties +---@field id integer diff --git a/api/lua/output.lua b/api/lua/output.lua index 1e4fbe5..b5dac89 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -8,6 +8,24 @@ ---@field name string The name of this output (or rather, of its connector). local op = {} +---Get all tags on this output. See `tag.get_on_output`. +---@return Tag[] +function op:tags() + return require("tag").get_on_output(self) +end + +---Add tags to this output. See `tag.add`. +---@param ... string The names of the tags you want to add. +function op:add_tags(...) + require("tag").add(self, ...) +end + +---Add tags to this output as a table. See `tag.add_table`. +---@param names string[] The names of the tags you want to add, as a table. +function op:add_tags_table(names) + require("tag").add_table(self, names) +end + ---Add methods to this output. ---@param props Output ---@return Output @@ -57,7 +75,7 @@ function output.get_by_name(name) end end ----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. +---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. @@ -113,6 +131,24 @@ function output.get_by_res(width, height) end ---Get the currently focused output. This is currently implemented as the one with the cursor on it. +--- +---This function may return nil, which means you may get a warning if you try to use it without checking for nil. +---Usually this function will not be nil unless you unplug all monitors, so instead of checking, +---you can ignore the warning by either forcing the type to be non-nil with an inline comment: +---```lua +---local op = output.get_focused() --[[@as Output]] +---``` +---or by disabling nil check warnings for the line: +---```lua +---local op = output.get_focused() +------@diagnostic disable-next-line:need-check-nil +---local tags_on_output = op:tags() +---``` +---Type checking done by Lua LS isn't perfect. +---Note that directly using the result of this function inline will *not* raise a warning, so be careful. +---```lua +---local tags = output.get_focused():tags() -- will NOT warn for nil +---``` ---@return Output|nil output The output, or nil if none are focused. function output.get_focused() SendMsg({ diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 326990c..3880506 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -13,6 +13,21 @@ ---| "CornerBottomLeft" # One main corner window in the bottom left with a column of windows on the right and a row on the top. ---| "CornerBottomRight" # One main corner window in the bottom right with a column of windows on the left and a row on the top. +---@class Tag +---@field private id integer The internal id of this tag. +local tg = {} + +---@param props Tag +---@return Tag +local function new_tag(props) + -- Copy functions over + for k, v in pairs(tg) do + props[k] = v + end + + return props +end + local tag = {} ---Add tags. @@ -53,12 +68,12 @@ end ---end ---``` ---@param output Output The output you want these tags to be added to. ----@param tags string[] The names of the new tags you want to add, as a table. -function tag.add_table(output, tags) +---@param names string[] The names of the new tags you want to add, as a table. +function tag.add_table(output, names) SendMsg({ AddTags = { output_name = output.name, - tags = tags, + tags = names, }, }) end @@ -156,4 +171,38 @@ function tag.set_layout(name, layout, output) end end end + +---Get all tags on the specified output. +--- +---You can also use `output_obj:tags()`, which delegates to this function: +---```lua +---local tags_on_output = output.get_focused():tags() +----- This is the same as +----- local tags_on_output = tag.get_on_output(output.get_focused()) +---``` +---@param output Output +---@return Tag[] +function tag.get_on_output(output) + SendMsg({ + Request = { + GetTagsByOutput = { + output = output.name, + }, + }, + }) + + local response = ReadMsg() + + local tag_props = response.RequestResponse.response.Tags.tags + + ---@type Tag[] + local tags = {} + + for _, prop in pairs(tag_props) do + table.insert(tags, new_tag({ id = prop.id })) + end + + return tags +end + return tag diff --git a/src/api/msg.rs b/src/api/msg.rs index e4930c8..482575d 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -9,6 +9,7 @@ use crate::{ layout::Layout, + tag::TagProperties, window::{window_state::WindowId, WindowProperties}, }; @@ -109,6 +110,7 @@ pub enum Request { GetOutputsByModel { model: String }, GetOutputsByRes { res: (u32, u32) }, GetOutputByFocus, + GetTagsByOutput { output: String }, } #[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] @@ -189,4 +191,5 @@ pub enum RequestResponse { Window { window: WindowProperties }, GetAllWindows { windows: Vec }, Outputs { names: Vec }, + Tags { tags: Vec }, } diff --git a/src/state.rs b/src/state.rs index 25d4b8e..056d3c0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -21,7 +21,7 @@ use crate::{ }, focus::FocusState, grab::resize_grab::ResizeSurfaceState, - tag::Tag, + tag::{Tag, TagProperties}, window::{window_state::WindowResizeState, WindowProperties}, }; use calloop::futures::Scheduler; @@ -310,12 +310,19 @@ impl State { self.loop_signal.stop(); } - Msg::Request(request) => match request { - Request::GetWindowByAppId { app_id } => todo!(), - Request::GetWindowByTitle { title } => todo!(), - Request::GetWindowByFocus => { - let Some(current_focus) = self.focus_state.current_focus() else { return; }; - let (app_id, title) = + Msg::Request(request) => { + let stream = self + .api_state + .stream + .as_ref() + .expect("Stream doesn't exist"); + let mut stream = stream.lock().expect("Couldn't lock stream"); + match request { + Request::GetWindowByAppId { app_id } => todo!(), + Request::GetWindowByTitle { title } => todo!(), + Request::GetWindowByFocus => { + let Some(current_focus) = self.focus_state.current_focus() else { return; }; + let (app_id, title) = compositor::with_states(current_focus.toplevel().wl_surface(), |states| { let lock = states .data_map @@ -325,38 +332,32 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = + let (window_id, floating) = current_focus.with_state(|state| (state.id, state.floating.is_floating())); - // TODO: unwrap - let location = self.space.element_location(¤t_focus).unwrap(); - let props = WindowProperties { - id: window_id, - app_id, - title, - size: current_focus.geometry().size.into(), - location: location.into(), - floating, - }; - 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::Window { window: props }, - }, - ) - .expect("Send to client failed"); - } - Request::GetAllWindows => { - let window_props = self - .space - .elements() - .map(|win| { - let (app_id, title) = + // TODO: unwrap + let location = self.space.element_location(¤t_focus).unwrap(); + let props = WindowProperties { + id: window_id, + app_id, + title, + size: current_focus.geometry().size.into(), + location: location.into(), + floating, + }; + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Window { window: props }, + }, + ) + .expect("Send to client failed"); + } + Request::GetAllWindows => { + let window_props = self + .space + .elements() + .map(|win| { + let (app_id, title) = compositor::with_states(win.toplevel().wl_surface(), |states| { let lock = states .data_map @@ -366,134 +367,130 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = + let (window_id, floating) = win.with_state(|state| (state.id, state.floating.is_floating())); - // TODO: unwrap - let location = self - .space - .element_location(win) - .expect("Window location doesn't exist"); - WindowProperties { - id: window_id, - app_id, - title, - size: win.geometry().size.into(), - location: location.into(), - floating, - } - }) - .collect::>(); + // TODO: unwrap + let location = self + .space + .element_location(win) + .expect("Window location doesn't exist"); + WindowProperties { + id: window_id, + app_id, + title, + size: win.geometry().size.into(), + location: location.into(), + floating, + } + }) + .collect::>(); - // FIXME: figure out what to do if error - 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::GetAllWindows { - windows: window_props, + // FIXME: figure out what to do if error + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::GetAllWindows { + windows: window_props, + }, }, - }, - ) - .expect("Couldn't send to client"); - } - Request::GetOutputByName { name } => { - let names = self - .space - .outputs() - .filter(|output| output.name() == name) - .map(|output| output.name()) - .collect::>(); - 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(); - } - Request::GetOutputsByModel { model } => { - let names = self - .space - .outputs() - .filter(|output| output.physical_properties().model == model) - .map(|output| output.name()) - .collect::>(); - 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(); - } - Request::GetOutputsByRes { res } => { - let names = self - .space - .outputs() - .filter_map(|output| { - if let Some(mode) = output.current_mode() { - if mode.size == (res.0 as i32, res.1 as i32).into() { - Some(output.name()) + ) + .expect("Couldn't send to client"); + } + Request::GetOutputByName { name } => { + // TODO: name better + let names = self + .space + .outputs() + .find(|output| output.name() == name) + .map(|output| output.name()); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { + names: if let Some(name) = names { + vec![name] + } else { + vec![] + } + }, + }, + ) + .unwrap(); + } + Request::GetOutputsByModel { model } => { + let names = self + .space + .outputs() + .filter(|output| output.physical_properties().model == model) + .map(|output| output.name()) + .collect::>(); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { names }, + }, + ) + .unwrap(); + } + Request::GetOutputsByRes { res } => { + let names = self + .space + .outputs() + .filter_map(|output| { + if let Some(mode) = output.current_mode() { + if mode.size == (res.0 as i32, res.1 as i32).into() { + Some(output.name()) + } else { + None + } } else { None } - } else { - None - } - }) - .collect::>(); - 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(); - } - Request::GetOutputByFocus => { - let names = self - .focus_state - .focused_output - .as_ref() - .map(|output| output.name()) - .into_iter() - .collect::>(); - 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(); + }) + .collect::>(); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { names }, + }, + ) + .unwrap(); + } + Request::GetOutputByFocus => { + let names = self + .focus_state + .focused_output + .as_ref() + .map(|output| output.name()) + .into_iter() + .collect::>(); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { names }, + }, + ) + .unwrap(); + } + Request::GetTagsByOutput { output } => { + let output = self + .space + .outputs() + .find(|op| op.name() == output); + if let Some(output) = output { + let tag_props = output.with_state(|state| { + state.tags + .iter() + .map(|tag| TagProperties { id: tag.id() }) + .collect::>() + }); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Tags { tags: tag_props } + }).unwrap(); + } + } } }, } diff --git a/src/tag.rs b/src/tag.rs index 9f866db..ed95841 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -90,6 +90,11 @@ impl Tag { } } +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TagProperties { + pub id: TagId, +} + impl State { pub fn output_for_tag(&self, tag: &Tag) -> Option { self.space From 92b10121a358ebf8ee9e1854c90a9568c788fa00 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 18 Jul 2023 10:41:55 -0500 Subject: [PATCH 26/27] Update README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 665e6c5..f5a7490 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -Cool stuff happens on the dev branch sometimes, check it out! - #
Pinnacle
@@ -18,13 +16,16 @@ Cool stuff happens on the dev branch sometimes, check it out! - [x] Udev backend - This is currently just a copy of Anvil's udev backend. - [x] Basic tags -- [ ] Widget system - [ ] Layout system -- [ ] Server-side decorations -- [ ] Animations and blur and all that pizazz -- [ ] The other stuff Awesome has + - [x] Left master stack, corner, dwindle, spiral layouts + - [ ] Other three master stack directions, floating, magnifier, maximized, and fullscreen layouts + - [ ] Resizable layouts - [ ] XWayland support - [ ] Layer-shell support +- [ ] Server-side decorations +- [ ] Animations and blur and all that pizazz +- [ ] Widget system +- [ ] The other stuff Awesome has - [x] Is very cool :thumbsup: ## Info From a6a62be4461684169fc7d906098804d598e592fb Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 18 Jul 2023 12:37:40 -0500 Subject: [PATCH 27/27] Add per tag layouts --- api/lua/example_config.lua | 46 ++++++++++++++++++++++++-------- api/lua/msg.lua | 4 +++ api/lua/output.lua | 24 +++++++---------- api/lua/pinnacle.lua | 3 +++ api/lua/tag.lua | 44 ++++++++++++++++++++++++++----- src/api.rs | 1 + src/api/msg.rs | 6 ++++- src/layout.rs | 54 +++++++++++++++++++++++++------------- src/state.rs | 39 +++++++++++++++++++++++++++ 9 files changed, 170 insertions(+), 51 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index cf858cf..33728d8 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -69,22 +69,46 @@ require("pinnacle").setup(function(pinnacle) "CornerBottomLeft", "CornerBottomRight", } - local index = 1 + local indices = {} + -- Layout cycling + -- Yes, this is very complicated and yes, I'll cook up a way to make it less complicated. input.keybind({ mod_key }, keys.space, function() - tag.set_layout("1", layouts[index]) - if index + 1 > #layouts then - index = 1 - else - index = index + 1 + local tags = output.get_focused():tags() + for _, tg in pairs(tags) do + if tg:active() then + local name = tg:name() + tg:set_layout(layouts[indices[name] or 1]) + if indices[name] == nil then + indices[name] = 2 + else + if indices[name] + 1 > #layouts then + indices[name] = 1 + else + indices[name] = indices[name] + 1 + end + end + break + end end end) input.keybind({ mod_key, "Shift" }, keys.space, function() - tag.set_layout("1", layouts[index]) - if index - 1 < 1 then - index = #layouts - else - index = index - 1 + local tags = output.get_focused():tags() + for _, tg in pairs(tags) do + if tg:active() then + local name = tg:name() + tg:set_layout(layouts[indices[name] or #layouts]) + if indices[name] == nil then + indices[name] = #layouts - 1 + else + if indices[name] - 1 < 1 then + indices[name] = #layouts + else + indices[name] = indices[name] - 1 + end + end + break + end end end) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index 1eb04fe..f541b4d 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -40,6 +40,8 @@ ---@field GetOutputsByModel { model: string } ---@field GetOutputsByRes { res: integer[] } ---@field GetTagsByOutput { output: string } +---@field GetTagActive { tag_id: integer } +---@field GetTagName { tag_id: integer } ---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus" @@ -56,6 +58,8 @@ ---@field GetAllWindows { windows: WindowProperties[] } ---@field Outputs { names: string[] } ---@field Tags { tags: TagProperties[] } +---@field TagActive { active: boolean } +---@field TagName { name: string } ---@class WindowProperties ---@field id integer diff --git a/api/lua/output.lua b/api/lua/output.lua index b5dac89..496465c 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -56,11 +56,9 @@ local output = {} ---@param name string The name of the output. ---@return Output|nil function output.get_by_name(name) - SendMsg({ - Request = { - GetOutputByName = { - name = name, - }, + SendRequest({ + GetOutputByName = { + name = name, }, }) @@ -82,11 +80,9 @@ end ---@param model string The model of the output(s). ---@return Output[] outputs All outputs with this model. If there are none, the returned table will be empty. function output.get_by_model(model) - SendMsg({ - Request = { - GetOutputsByModel = { - model = model, - }, + SendRequest({ + GetOutputsByModel = { + model = model, }, }) @@ -109,11 +105,9 @@ end ---@param height integer The height of the outputs, in pixels. ---@return Output[] outputs All outputs with this resolution. If there are none, the returned table will be empty. function output.get_by_res(width, height) - SendMsg({ - Request = { - GetOutputsByRes = { - res = { width, height }, - }, + SendRequest({ + GetOutputsByRes = { + res = { width, height }, }, }) diff --git a/api/lua/pinnacle.lua b/api/lua/pinnacle.lua index 7cc0965..55aaf91 100644 --- a/api/lua/pinnacle.lua +++ b/api/lua/pinnacle.lua @@ -19,6 +19,7 @@ local function read_exact(socket_fd, count) local len_to_read = count local data = "" while len_to_read > 0 do + -- print("need to read " .. tostring(len_to_read) .. " bytes") local bytes, err_msg, errnum = socket.recv(socket_fd, len_to_read) if bytes == nil then @@ -105,9 +106,11 @@ function pinnacle.setup(config_func) ---@type integer local msg_len = string.unpack("=I4", msg_len_bytes) + -- print(msg_len) local msg_bytes, err_msg2, err_num2 = read_exact(socket_fd, msg_len) assert(msg_bytes) + -- print(msg_bytes) ---@type IncomingMsg local tb = msgpack.decode(msg_bytes) diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 3880506..31b337a 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -4,6 +4,8 @@ -- -- SPDX-License-Identifier: MPL-2.0 +local tag = {} + ---@alias Layout ---| "MasterStack" # One master window on the left with all other windows stacked to the right. ---| "Dwindle" # Windows split in half towards the bottom right corner. @@ -28,7 +30,39 @@ local function new_tag(props) return props end -local tag = {} +---Get this tag's active status. +---@return boolean active True if the tag is active, otherwise false. +function tg:active() + SendRequest({ + GetTagActive = { + tag_id = self.id, + }, + }) + + local response = ReadMsg() + local active = response.RequestResponse.response.TagActive.active + return active +end + +function tg:name() + SendRequest({ + GetTagName = { + tag_id = self.id, + }, + }) + + local response = ReadMsg() + local name = response.RequestResponse.response.TagName.name + return name +end + +---Set this tag's layout. +---@param layout Layout +function tg:set_layout(layout) -- TODO: output param + tag.set_layout(self:name(), layout) +end + +----------------------------------------------------------- ---Add tags. --- @@ -183,11 +217,9 @@ end ---@param output Output ---@return Tag[] function tag.get_on_output(output) - SendMsg({ - Request = { - GetTagsByOutput = { - output = output.name, - }, + SendRequest({ + GetTagsByOutput = { + output = output.name, }, }) diff --git a/src/api.rs b/src/api.rs index effabef..e28b8ff 100644 --- a/src/api.rs +++ b/src/api.rs @@ -112,6 +112,7 @@ pub fn send_to_client( stream: &mut UnixStream, msg: &OutgoingMsg, ) -> Result<(), rmp_serde::encode::Error> { + // tracing::debug!("Sending {msg:?}"); let msg = rmp_serde::to_vec_named(msg)?; let msg_len = msg.len() as u32; let bytes = msg_len.to_ne_bytes(); diff --git a/src/api/msg.rs b/src/api/msg.rs index 482575d..4553742 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -9,7 +9,7 @@ use crate::{ layout::Layout, - tag::TagProperties, + tag::{TagId, TagProperties}, window::{window_state::WindowId, WindowProperties}, }; @@ -111,6 +111,8 @@ pub enum Request { GetOutputsByRes { res: (u32, u32) }, GetOutputByFocus, GetTagsByOutput { output: String }, + GetTagActive { tag_id: TagId }, + GetTagName { tag_id: TagId }, } #[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] @@ -192,4 +194,6 @@ pub enum RequestResponse { GetAllWindows { windows: Vec }, Outputs { names: Vec }, Tags { tags: Vec }, + TagActive { active: bool }, + TagName { name: String }, } diff --git a/src/layout.rs b/src/layout.rs index 78c83a1..baeb6f4 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -167,8 +167,10 @@ impl Layout { Slice::Right => { let width_partition = win1_size.w / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w - width_partition, win1_size.h).into()); + state.size = Some( + (win1_size.w - width_partition, i32::max(win1_size.h, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -177,7 +179,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((width_partition, win1_size.h).into()); + state.size = + Some((width_partition, i32::max(win1_size.h, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -190,8 +193,10 @@ impl Layout { Slice::Below => { let height_partition = win1_size.h / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w, win1_size.h - height_partition).into()); + state.size = Some( + (win1_size.w, i32::max(win1_size.h - height_partition, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -200,7 +205,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((win1_size.w, height_partition).into()); + state.size = + Some((win1_size.w, i32::max(height_partition, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -273,8 +279,10 @@ impl Layout { Slice::Above => { let height_partition = win1_size.h / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w, win1_size.h - height_partition).into()); + state.size = Some( + (win1_size.w, i32::max(win1_size.h - height_partition, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -283,7 +291,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((win1_size.w, height_partition).into()); + state.size = + Some((win1_size.w, i32::max(height_partition, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -295,8 +304,10 @@ impl Layout { Slice::Below => { let height_partition = win1_size.h / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w, win1_size.h - height_partition).into()); + state.size = Some( + (win1_size.w, win1_size.h - i32::max(height_partition, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -305,7 +316,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((win1_size.w, height_partition).into()); + state.size = + Some((win1_size.w, i32::max(height_partition, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -318,8 +330,10 @@ impl Layout { Slice::Left => { let width_partition = win1_size.w / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w - width_partition, win1_size.h).into()); + state.size = Some( + (win1_size.w - width_partition, i32::max(win1_size.h, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -328,7 +342,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((width_partition, win1_size.h).into()); + state.size = + Some((width_partition, i32::max(win1_size.h, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -340,8 +355,10 @@ impl Layout { Slice::Right => { let width_partition = win1_size.w / 2; win1.toplevel().with_pending_state(|state| { - state.size = - Some((win1_size.w - width_partition, win1_size.h).into()); + state.size = Some( + (win1_size.w - width_partition, i32::max(win1_size.h, 40)) + .into(), + ); }); win1.with_state(|state| { state.resize_state = WindowResizeState::Requested( @@ -350,7 +367,8 @@ impl Layout { ); }); win2.toplevel().with_pending_state(|state| { - state.size = Some((width_partition, win1_size.h).into()); + state.size = + Some((width_partition, i32::max(win1_size.h, 40)).into()); }); win2.with_state(|state| { state.resize_state = WindowResizeState::Requested( diff --git a/src/state.rs b/src/state.rs index 056d3c0..9fb4a60 100644 --- a/src/state.rs +++ b/src/state.rs @@ -105,6 +105,7 @@ pub struct State { impl State { pub fn handle_msg(&mut self, msg: Msg) { + // tracing::debug!("Got {msg:?}"); match msg { Msg::SetKeybind { key, @@ -491,6 +492,44 @@ impl State { }).unwrap(); } } + Request::GetTagActive { tag_id } => { + let tag = self + .space + .outputs() + .flat_map(|op| { + op.with_state(|state| state.tags.clone()) + }) + .find(|tag| tag.id() == tag_id); + if let Some(tag) = tag { + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::TagActive { + active: tag.active() + } + }) + .unwrap(); + } + } + Request::GetTagName { tag_id } => { + let tag = self + .space + .outputs() + .flat_map(|op| { + op.with_state(|state| state.tags.clone()) + }) + .find(|tag| tag.id() == tag_id); + if let Some(tag) = tag { + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::TagName { + name: tag.name() + } + }) + .unwrap(); + } + } } }, }