diff --git a/api/lua/msg.lua b/api/lua/msg.lua index a6ec9ae..ff845cf 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -11,6 +11,8 @@ ---@field SetWindowSize { window_id: WindowId, width: integer?, height: integer? }? ---@field MoveWindowToTag { window_id: WindowId, tag_id: TagId }? ---@field ToggleTagOnWindow { window_id: WindowId, tag_id: TagId }? +---@field SetStatus { window_id: WindowId, status: StatusName }? +-- ---@field Spawn { command: string[], callback_id: integer? }? ---@field Request Request? --Tags @@ -25,6 +27,12 @@ ---@alias Msg _Msg | "Quit" +---@alias StatusName +---| "Floating" +---| "Tiled" +---| "Fullscreen" +---| "Maximized" + -------------------------------------------------------------------------------------------- ---@class __Request diff --git a/api/lua/test_config.lua b/api/lua/test_config.lua index 796b522..407dbeb 100644 --- a/api/lua/test_config.lua +++ b/api/lua/test_config.lua @@ -64,6 +64,27 @@ require("pinnacle").setup(function(pinnacle) process.spawn("nautilus") end) + input.keybind({ mod_key }, keys.f, function() + local win = window.get_focused() + if win ~= nil then + win:set_status("Fullscreen") + end + end) + + input.keybind({ mod_key }, keys.m, function() + local win = window.get_focused() + if win ~= nil then + win:set_status("Maximized") + end + end) + + input.keybind({ mod_key }, keys.t, function() + local win = window.get_focused() + if win ~= nil then + win:set_status("Tiled") + end + end) + -- Just testing stuff input.keybind({ mod_key }, keys.h, function() local dp2 = output.get_by_name("DP-2") diff --git a/api/lua/window.lua b/api/lua/window.lua index 73c2eec..0188fa7 100644 --- a/api/lua/window.lua +++ b/api/lua/window.lua @@ -101,6 +101,12 @@ function window:toggle_floating() window_module.toggle_floating(self) end +---Set this window's status. Yes, this isn't a great name. +---@param status StatusName +function window:set_status(status) + window_module.set_status(self, status) +end + ---Get this window's size. --- ---### Example @@ -241,7 +247,8 @@ end ---Get all windows. ---@return Window[] function window_module.get_all() - local window_ids = Request("GetWindows").RequestResponse.response.Windows.window_ids + local window_ids = + Request("GetWindows").RequestResponse.response.Windows.window_ids ---@type Window[] local windows = {} for _, window_id in pairs(window_ids) do @@ -383,6 +390,18 @@ function window_module.toggle_floating(win) }) end +---Set `win`'s status. Yes, this is not a great name for the function. +---@param win Window +---@param status StatusName +function window_module.set_status(win, status) + SendMsg({ + SetStatus = { + window_id = win:id(), + status = status, + }, + }) +end + ---Get the specified window's size. --- ---### Example diff --git a/src/api/msg.rs b/src/api/msg.rs index df78935..861c17f 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -3,7 +3,12 @@ // 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::{layout::Layout, output::OutputName, tag::TagId, window::window_state::WindowId}; +use crate::{ + layout::Layout, + output::OutputName, + tag::TagId, + window::window_state::{StatusName, WindowId}, +}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); @@ -42,6 +47,10 @@ pub enum Msg { window_id: WindowId, tag_id: TagId, }, + SetStatus { + window_id: WindowId, + status: StatusName, + }, // Tag management ToggleTag { diff --git a/src/backend.rs b/src/backend.rs index 49706b2..90de35d 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -37,7 +37,8 @@ pub fn post_repaint( dmabuf_feedback: Option>, time: Duration, ) { - let throttle = Some(Duration::from_secs(1)); + // let throttle = Some(Duration::from_secs(1)); + let throttle = Some(Duration::ZERO); space.elements().for_each(|window| { window.with_surfaces(|surface, states_inner| { diff --git a/src/focus.rs b/src/focus.rs index c902be8..7653b1d 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use smithay::{ - desktop::{LayerSurface, PopupKind}, + desktop::{LayerSurface, PopupKind, Space}, input::{ keyboard::KeyboardTarget, pointer::{MotionEvent, PointerTarget}, @@ -17,7 +17,7 @@ use crate::{backend::Backend, state::State, window::WindowElement}; #[derive(Default)] pub struct FocusState { - focus_stack: Vec, + pub focus_stack: Vec, pub focused_output: Option, } @@ -43,6 +43,18 @@ impl FocusState { self.focus_stack.retain(|win| win != &window); self.focus_stack.push(window); } + + /// Fix focus layering for all windows in the `focus_stack`. + /// + /// This will call `space.map_element` on all windows from front + /// to back to correct their z locations. + pub fn fix_up_focus(&self, space: &mut Space) { + for win in self.focus_stack.iter() { + if let Some(loc) = space.element_location(win) { + space.map_element(win.clone(), loc, false); + } + } + } } #[derive(Debug, Clone, PartialEq)] diff --git a/src/handlers.rs b/src/handlers.rs index 1e5879c..beeefd1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -122,13 +122,13 @@ impl CompositorHandler for State { window.with_state(|state| { if let LocationRequestState::Acknowledged(new_pos) = state.loc_request_state { state.loc_request_state = LocationRequestState::Idle; - if window.is_x11() { - tracing::warn!("did something with X11 window here"); - } self.space.map_element(window.clone(), new_pos, false); } }); } + + // correct focus layering + self.focus_state.fix_up_focus(&mut self.space); } fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 476baaa..f916013 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -30,7 +30,10 @@ use crate::{ backend::Backend, focus::FocusTarget, state::{State, WithState}, - window::{window_state::LocationRequestState, WindowBlocker, WindowElement, BLOCKER_COUNTER}, + window::{ + window_state::{LocationRequestState, StatusName}, + WindowElement, BLOCKER_COUNTER, + }, }; impl XdgShellHandler for State { @@ -39,17 +42,9 @@ impl XdgShellHandler for State { } fn new_toplevel(&mut self, surface: ToplevelSurface) { - let window = WindowElement::Wayland(Window::new(surface)); + let window = WindowElement::Wayland(Window::new(surface.clone())); - { - let WindowElement::Wayland(window) = &window else { unreachable!() }; - 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.set_status(StatusName::Tiled); window.with_state(|state| { state.tags = match ( @@ -92,6 +87,8 @@ impl XdgShellHandler for State { .cloned() .collect::>(); + // note to self: don't reorder this + // TODO: fix it so that reordering this doesn't break stuff 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() { @@ -103,7 +100,7 @@ impl XdgShellHandler for State { ); for win in windows_on_output.iter() { if let Some(surf) = win.wl_surface() { - compositor::add_blocker(&surf, WindowBlocker); + compositor::add_blocker(&surf, crate::window::WindowBlocker); } } let clone = window.clone(); @@ -146,17 +143,7 @@ impl XdgShellHandler for State { .is_some_and(|surf| &surf != surface.wl_surface()) }); if let Some(focused_output) = self.focus_state.focused_output.as_ref().cloned() { - 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(), - &mut self.space, - &focused_output, - ); - } - }); + self.update_windows(&focused_output); } // let mut windows: Vec = self.space.elements().cloned().collect(); diff --git a/src/state.rs b/src/state.rs index d28f44c..2a16017 100644 --- a/src/state.rs +++ b/src/state.rs @@ -206,6 +206,18 @@ impl State { self.update_windows(&output); // self.re_layout(&output); } + Msg::SetStatus { window_id, status } => { + let Some(window) = window_id.window(self) else { return }; + window.set_status(status); + let outputs = self.space.outputs_for_element(&window); + self.focus_state.set_focus(window); + + if let Some(output) = outputs.into_iter().next() { + self.update_windows(&output); + } + } + + // Tags ---------------------------------------- Msg::ToggleTag { tag_id } => { tracing::debug!("ToggleTag"); if let Some(tag) = tag_id.tag(self) { @@ -735,6 +747,10 @@ pub fn schedule_on_commit( for window in windows.iter().filter(|win| win.alive()) { if window.with_state(|state| !matches!(state.loc_request_state, LocationRequestState::Idle)) { + tracing::debug!( + "window state is {:?}", + window.with_state(|state| state.loc_request_state.clone()) + ); data.state.loop_handle.insert_idle(|data| { schedule_on_commit(data, windows, on_commit); }); diff --git a/src/window.rs b/src/window.rs index 38c828d..e82f4ae 100644 --- a/src/window.rs +++ b/src/window.rs @@ -40,7 +40,7 @@ use crate::{ state::{State, WithState}, }; -use self::window_state::{LocationRequestState, Status, WindowElementState}; +use self::window_state::{LocationRequestState, Status, StatusName, WindowElementState}; pub mod window_state; @@ -269,104 +269,73 @@ impl WindowElement { matches!(self, Self::X11(..)) } - /// Set this window to floating. - /// - /// This will change the size of the window only. - /// Call `State.update_windows` to perform mapping. - pub fn set_floating(&self) { - let status = self.with_state(|state| state.status); + pub fn set_status(&self, status: StatusName) { + let prev_status = self.with_state(|state| state.status); + let geo = match prev_status { + Status::Floating(rect) + | Status::Tiled(Some(rect)) + | Status::Fullscreen(Some(rect)) + | Status::Maximized(Some(rect)) => rect, + _ => self.geometry(), + }; + match status { - Status::Floating(_) => (), - Status::Tiled(rect) | Status::Fullscreen(rect) | Status::Maximized(rect) => { - if let Some(rect) = rect { - self.change_geometry(rect); - self.with_state(|state| state.status = Status::Floating(rect)); - } else { - // TODO: is this the same as from the space? prolly not - let geo = self.geometry(); - self.with_state(|state| state.status = Status::Floating(geo)); + StatusName::Floating => { + self.with_state(|state| state.status = Status::Floating(geo)); + + if let WindowElement::Wayland(window) = self { + window.toplevel().with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.states.unset(xdg_toplevel::State::Fullscreen); + state.states.unset(xdg_toplevel::State::TiledTop); + state.states.unset(xdg_toplevel::State::TiledBottom); + state.states.unset(xdg_toplevel::State::TiledLeft); + state.states.unset(xdg_toplevel::State::TiledRight); + }); } } - } + StatusName::Tiled => { + self.with_state(|state| state.status = Status::Tiled(Some(geo))); - if let WindowElement::Wayland(window) = self { - window.toplevel().with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.states.unset(xdg_toplevel::State::Fullscreen); - state.states.unset(xdg_toplevel::State::TiledTop); - state.states.unset(xdg_toplevel::State::TiledBottom); - state.states.unset(xdg_toplevel::State::TiledLeft); - state.states.unset(xdg_toplevel::State::TiledRight); - }); - } - } + if let WindowElement::Wayland(window) = self { + window.toplevel().with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.states.unset(xdg_toplevel::State::Fullscreen); + state.states.set(xdg_toplevel::State::TiledTop); + state.states.set(xdg_toplevel::State::TiledBottom); + state.states.set(xdg_toplevel::State::TiledLeft); + state.states.set(xdg_toplevel::State::TiledRight); + }); + } + } + StatusName::Fullscreen => { + self.with_state(|state| state.status = Status::Fullscreen(Some(geo))); - /// Call compute_tiled_windows after this - pub fn set_tiled(&self) { - let geo = match self.with_state(|state| state.status) { - Status::Floating(rect) - | Status::Tiled(Some(rect)) - | Status::Fullscreen(Some(rect)) - | Status::Maximized(Some(rect)) => rect, - _ => self.geometry(), - }; - self.with_state(|state| state.status = Status::Tiled(Some(geo))); + if let WindowElement::Wayland(window) = self { + window.toplevel().with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.states.set(xdg_toplevel::State::Fullscreen); + state.states.set(xdg_toplevel::State::TiledTop); + state.states.set(xdg_toplevel::State::TiledBottom); + state.states.set(xdg_toplevel::State::TiledLeft); + state.states.set(xdg_toplevel::State::TiledRight); + }); + } + } + StatusName::Maximized => { + self.with_state(|state| state.status = Status::Maximized(Some(geo))); - if let WindowElement::Wayland(window) = self { - window.toplevel().with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.states.unset(xdg_toplevel::State::Fullscreen); - state.states.set(xdg_toplevel::State::TiledTop); - state.states.set(xdg_toplevel::State::TiledBottom); - state.states.set(xdg_toplevel::State::TiledLeft); - state.states.set(xdg_toplevel::State::TiledRight); - }); - } - } - - pub fn set_fullscreen(&self) { - let geo = match self.with_state(|state| state.status) { - Status::Floating(rect) - | Status::Tiled(Some(rect)) - | Status::Fullscreen(Some(rect)) - | Status::Maximized(Some(rect)) => rect, - _ => self.geometry(), - }; - - self.with_state(|state| state.status = Status::Fullscreen(Some(geo))); - - if let WindowElement::Wayland(window) = self { - window.toplevel().with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.states.set(xdg_toplevel::State::Fullscreen); - state.states.set(xdg_toplevel::State::TiledTop); - state.states.set(xdg_toplevel::State::TiledBottom); - state.states.set(xdg_toplevel::State::TiledLeft); - state.states.set(xdg_toplevel::State::TiledRight); - }); - } - } - - pub fn set_maximized(&self) { - let geo = match self.with_state(|state| state.status) { - Status::Floating(rect) - | Status::Tiled(Some(rect)) - | Status::Fullscreen(Some(rect)) - | Status::Maximized(Some(rect)) => rect, - _ => self.geometry(), - }; - - self.with_state(|state| state.status = Status::Maximized(Some(geo))); - - if let WindowElement::Wayland(window) = self { - window.toplevel().with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Fullscreen); - state.states.set(xdg_toplevel::State::Maximized); - state.states.set(xdg_toplevel::State::TiledTop); - state.states.set(xdg_toplevel::State::TiledBottom); - state.states.set(xdg_toplevel::State::TiledLeft); - state.states.set(xdg_toplevel::State::TiledRight); - }); + if let WindowElement::Wayland(window) = self { + window.toplevel().with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Fullscreen); + state.states.set(xdg_toplevel::State::Maximized); + state.states.set(xdg_toplevel::State::TiledTop); + state.states.set(xdg_toplevel::State::TiledBottom); + state.states.set(xdg_toplevel::State::TiledLeft); + state.states.set(xdg_toplevel::State::TiledRight); + }); + } + } } } } @@ -604,10 +573,11 @@ impl State { /// Toggle a window's floating status. pub fn toggle_floating(state: &mut State, window: &WindowElement) { - if window.with_state(|state| state.status.is_floating()) { - window.set_tiled(); - } else { - window.set_floating(); + match window.with_state(|state| state.status) { + Status::Floating(_) => window.set_status(StatusName::Tiled), + Status::Tiled(_) => window.set_status(StatusName::Fullscreen), + Status::Fullscreen(_) => window.set_status(StatusName::Maximized), + Status::Maximized(_) => window.set_status(StatusName::Floating), } // TODO: don't use the focused output, use the one the window is on @@ -637,6 +607,7 @@ pub fn toggle_floating(state: &mut State, window: &WindowElement) false }) }) + .filter(|win| win != window) .collect::>() }); diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 41e5dee..2984e58 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -122,6 +122,15 @@ pub enum Status { Fullscreen(Option>), Maximized(Option>), } +// TODO: couple these somehow + +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub enum StatusName { + Floating, + Tiled, + Fullscreen, + Maximized, +} impl Status { /// Returns `true` if the float is [`Tiled`].