diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 2791a78..c69be4f 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -14,6 +14,7 @@ require("pinnacle").setup(function(pinnacle) local input = pinnacle.input -- Key and mouse binds local client = pinnacle.client -- Window management local process = pinnacle.process -- Process spawning + local tag = pinnacle.tag -- Tag management -- Every key supported by xkbcommon. -- Support for just putting in a string of a key is intended. @@ -41,4 +42,24 @@ require("pinnacle").setup(function(pinnacle) input.keybind({ "Ctrl" }, keys.KEY_3, function() process.spawn("nautilus") end) + + -- Tags --------------------------------------------------------------------------- + tag.add("1", "2", "3", "4", "5") + tag.toggle("1") + + input.keybind({ "Ctrl", "Shift" }, keys.KEY_1, function() + tag.toggle("1") + end) + input.keybind({ "Ctrl", "Shift" }, keys.KEY_2, function() + tag.toggle("2") + end) + input.keybind({ "Ctrl", "Shift" }, keys.KEY_3, function() + tag.toggle("3") + end) + input.keybind({ "Ctrl", "Shift" }, keys.KEY_4, function() + tag.toggle("4") + end) + input.keybind({ "Ctrl", "Shift" }, keys.KEY_5, function() + tag.toggle("5") + end) end) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index e96e827..f2d7eb7 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -14,6 +14,10 @@ ---@field SetWindowSize { window_id: integer, size: { w: integer, h: integer } } ---@field Spawn { command: string[], callback_id: integer? } ---@field Request Request +--Tags +---@field ToggleTag { tag_id: string } +---@field AddTags { tags: string[] } +---@field RemoveTags { tags: string[] } ---@alias Msg _Msg | "Quit" diff --git a/api/lua/pinnacle.lua b/api/lua/pinnacle.lua index e6dfb2e..51187ab 100644 --- a/api/lua/pinnacle.lua +++ b/api/lua/pinnacle.lua @@ -54,6 +54,8 @@ local pinnacle = { client = require("client"), ---Process spawning process = require("process"), + ---Tag management + tag = require("tag"), } ---Quit Pinnacle. diff --git a/api/lua/tag.lua b/api/lua/tag.lua new file mode 100644 index 0000000..c04b559 --- /dev/null +++ b/api/lua/tag.lua @@ -0,0 +1,48 @@ +-- 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 + +local tag = {} + +---Add tags. +--- +---# Example +--- +---```lua +---tag.add("1", "2", "3", "4", "5") -- Add tags with names 1-5 +---``` +---@param ... string The names of the new tags you want to add. +function tag.add(...) + local tags = table.pack(...) + tags["n"] = nil + + SendMsg({ + AddTags = { + tags = tags, + }, + }) +end + +---Like `tag.add(...)`, but with a table of strings instead. +---@param tags string[] The names of the new tags you want to add, as a table. +function tag.add_table(tags) + SendMsg({ + AddTags = { + tags = tags, + }, + }) +end + +---Toggle a tag's display. +---@param name string The name of the tag. +function tag.toggle(name) + SendMsg({ + ToggleTag = { + tag_id = name, + }, + }) +end + +return tag diff --git a/src/api/msg.rs b/src/api/msg.rs index 8eef078..43b882c 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::{tag::Tag, window_state::WindowId, WindowProperties}; +use crate::{ + tag::TagId, + window::{window_state::WindowId, WindowProperties}, +}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); @@ -33,17 +36,25 @@ pub enum Msg { #[serde(default)] client_id: Option, }, - MoveToTag { - tag: Tag, - }, - ToggleTag { - tag: Tag, - }, SetWindowSize { window_id: WindowId, size: (i32, i32), }, + // Tag management + MoveToTag { + tag_id: TagId, + }, + ToggleTag { + tag_id: TagId, + }, + AddTags { + tags: Vec, + }, + RemoveTags { + tags: Vec, + }, + // Process management /// Spawn a program with an optional callback. Spawn { diff --git a/src/backend/winit.rs b/src/backend/winit.rs index d740e25..b9ddc43 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -387,7 +387,9 @@ pub fn run_winit() -> Result<(), Box> { event_loop.run( Some(Duration::from_millis(6)), &mut CalloopData { display, state }, - |_data| {}, + |_data| { + // println!("{}", _data.state.space.elements().count()); + }, )?; Ok(()) diff --git a/src/focus.rs b/src/focus.rs index e63f2fa..26226d2 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -17,6 +17,7 @@ impl FocusState { Default::default() } + /// Get the currently focused window. pub fn current_focus(&mut self) -> Option { while let Some(window) = self.focus_stack.last() { if window.alive() { @@ -27,6 +28,7 @@ impl FocusState { None } + /// Set the currently focused window. pub fn set_focus(&mut self, window: Window) { self.focus_stack.retain(|win| win != &window); self.focus_stack.push(window); diff --git a/src/handlers.rs b/src/handlers.rs index 900a7e2..60666e4 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -48,6 +48,7 @@ use smithay::{ use crate::{ backend::Backend, layout::Layout, + output::OutputState, state::{ClientState, State}, window::window_state::{WindowResizeState, WindowState}, }; @@ -116,7 +117,7 @@ impl CompositorHandler for State { if let Some(window) = self.window_for_surface(surface) { WindowState::with_state(&window, |state| { if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state { - // tracing::info!("Committing, new location"); + tracing::info!("Committing, new location"); state.resize_state = WindowResizeState::Idle; self.space.map_element(window.clone(), new_pos, false); } @@ -224,6 +225,18 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new(surface); + 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.to_vec()) + } else if let Some(first_tag) = self.tag_state.tags.first() { + vec![first_tag.id.clone()] + } else { + vec![] + }; + tracing::info!("new window, tags are {:?}", state.tags); + }); + + self.windows.push(window.clone()); self.space.map_element(window.clone(), (0, 0), true); self.loop_handle.insert_idle(move |data| { data.state @@ -236,6 +249,7 @@ impl XdgShellHandler for State { SERIAL_COUNTER.next_serial(), ); }); + let windows: Vec = self.space.elements().cloned().collect(); self.loop_handle.insert_idle(|data| { @@ -345,15 +359,18 @@ impl XdgShellHandler for State { } fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { - // TODO: add serial to WaitingForAck + tracing::info!("ack configure start"); if let Some(window) = self.window_for_surface(&surface) { + tracing::info!("in window_for_surface"); WindowState::with_state(&window, |state| { if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state { match &configure { Configure::Toplevel(configure) => { // tracing::info!("acking before serial check"); + tracing::info!("configure serial: {:?}", configure.serial); + tracing::info!("provided serial: {:?}", serial); if configure.serial >= serial { - // tracing::info!("acking, serial >="); + tracing::info!("acking, serial >="); state.resize_state = WindowResizeState::WaitingForCommit(new_loc); } } @@ -364,6 +381,12 @@ impl XdgShellHandler for State { } } + // fn minimize_request(&mut self, surface: ToplevelSurface) { + // if let Some(window) = self.window_for_surface(surface.wl_surface()) { + // self.space.unmap_elem(&window); + // } + // } + // TODO: impl the rest of the fns in XdgShellHandler } delegate_xdg_shell!(@ State); diff --git a/src/input.rs b/src/input.rs index ae1f52d..32f64fd 100644 --- a/src/input.rs +++ b/src/input.rs @@ -234,10 +234,15 @@ impl State { if modifiers.logo { modifier_mask.push(Modifiers::Super); } + let raw_sym = if keysym.raw_syms().len() == 1 { + keysym.raw_syms()[0] + } else { + keysyms::KEY_NoSymbol + }; if let Some(callback_id) = state .input_state .keybinds - .get(&(modifier_mask.into(), keysym.modified_sym())) + .get(&(modifier_mask.into(), raw_sym)) { return FilterResult::Intercept(*callback_id); } diff --git a/src/layout/automatic.rs b/src/layout/automatic.rs index d71c862..8ac84c9 100644 --- a/src/layout/automatic.rs +++ b/src/layout/automatic.rs @@ -26,6 +26,7 @@ impl Layout { windows.retain(|win| WindowState::with_state(win, |state| state.floating.is_tiled())); match side { Direction::Left => { + tracing::info!("Laying out windows"); let window_count = windows.len(); if window_count == 0 { return; @@ -42,6 +43,7 @@ impl Layout { // TODO: no idea what happens if you spawn a window while no monitors are // | connected, figure that out }; + tracing::info!("got output"); let output_size = state.space.output_geometry(output).unwrap().size; if window_count == 1 { let window = windows[0].clone(); @@ -72,6 +74,7 @@ impl Layout { return; } + tracing::warn!("layed out first window"); let mut windows = windows.iter(); let first_window = windows.next().unwrap(); @@ -92,6 +95,7 @@ impl Layout { .initial_configure_sent }); if initial_configure_sent { + tracing::info!("sending resize thingy first_window"); WindowState::with_state(first_window, |state| { state.resize_state = WindowResizeState::WaitingForAck( first_window.toplevel().send_configure(), @@ -144,6 +148,7 @@ impl Layout { .initial_configure_sent }); if initial_configure_sent { + tracing::info!("sending resize thingy"); WindowState::with_state(win, |state| { state.resize_state = WindowResizeState::WaitingForAck( win.toplevel().send_configure(), diff --git a/src/main.rs b/src/main.rs index 2cc04a5..d1634bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ mod output; mod pointer; mod render; mod state; +mod tag; mod window; mod xdg; diff --git a/src/output.rs b/src/output.rs index 6466c13..ba67068 100644 --- a/src/output.rs +++ b/src/output.rs @@ -8,11 +8,11 @@ use std::cell::RefCell; use smithay::output::Output; -use crate::window::tag::Tag; +use crate::tag::TagId; #[derive(Default)] pub struct OutputState { - focused_tags: Vec, + pub focused_tags: Vec, } impl OutputState { @@ -22,7 +22,7 @@ impl OutputState { { output .user_data() - .insert_if_missing(|| RefCell::::default); + .insert_if_missing(RefCell::::default); let state = output .user_data() diff --git a/src/state.rs b/src/state.rs index b8f9742..13f4957 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,7 +18,7 @@ use crate::{ PinnacleSocketSource, }, focus::FocusState, - window::{window_state::WindowState, WindowProperties}, + window::{window_state::WindowState, WindowProperties}, output::OutputState, tag::{TagState, Tag}, layout::Layout, }; use calloop::futures::Scheduler; use futures_lite::AsyncBufReadExt; @@ -84,11 +84,13 @@ pub struct State { pub input_state: InputState, pub api_state: ApiState, pub focus_state: FocusState, + pub tag_state: TagState, pub popup_manager: PopupManager, pub cursor_status: CursorImageStatus, pub pointer_location: Point, + pub windows: Vec, pub async_scheduler: Scheduler<()>, } @@ -181,8 +183,6 @@ impl State { } => { data.state.handle_spawn(command, callback_id); } - Msg::MoveToTag { tag } => todo!(), - Msg::ToggleTag { tag } => todo!(), Msg::SetWindowSize { window_id, size } => { let Some(window) = data.state.space.elements().find(|&win| { @@ -195,6 +195,57 @@ impl State { }); window.toplevel().send_pending_configure(); } + Msg::MoveToTag { tag_id } => todo!(), + Msg::ToggleTag { tag_id } => { + let windows = OutputState::with(data.state.focus_state.focused_output.as_ref().unwrap(), |state| { + if state.focused_tags + .iter() + .any(|tg| tg == &tag_id) + { + tracing::info!("Toggle tag {tag_id:?} off"); + state.focused_tags.retain(|tg| tg != &tag_id); + } else { + tracing::info!("Toggle tag {tag_id:?} on"); + state.focused_tags.push(tag_id.clone()); + } + // re-layout + for window in data.state.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.contains(tag_id) { + return true; + } + } + false + }); + if !should_render { + data.state.space.unmap_elem(&window); + } + } + + data.state.windows.iter().filter(|&win| { + WindowState::with_state(win, |win_state| { + for tag_id in win_state.tags.iter() { + if state.focused_tags.contains(tag_id) { + return true; + } + } + false + }) + }).cloned().collect::>() + + }); + + tracing::info!("Laying out {} windows", windows.len()); + + Layout::master_stack(&mut data.state, windows, crate::layout::Direction::Left); + }, + Msg::AddTags { tags } => { + data.state.tag_state.tags.extend(tags.into_iter().map(|tag| Tag { id: tag, windows: vec![] })); + }, + Msg::RemoveTags { tags } => { + data.state.tag_state.tags.retain(|tag| !tags.contains(&tag.id)); + }, Msg::Quit => { data.state.loop_signal.stop(); @@ -365,6 +416,7 @@ impl State { input_state: InputState::new(), api_state: ApiState::new(), focus_state: FocusState::new(), + tag_state: TagState::new(), seat, @@ -374,6 +426,8 @@ impl State { popup_manager: PopupManager::default(), async_scheduler: sched, + + windows: vec![], }) } @@ -412,7 +466,6 @@ impl State { return; }; - // TODO: find a way to make this hellish code look better, deal with unwraps if let Some(callback_id) = callback_id { let stdout = child.stdout.take(); let stderr = child.stderr.take(); @@ -535,8 +588,6 @@ impl ClientData for ClientState { fn initialized(&self, _client_id: ClientId) {} fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} - - // fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {} } #[derive(Debug, Copy, Clone)] diff --git a/src/tag.rs b/src/tag.rs new file mode 100644 index 0000000..228d208 --- /dev/null +++ b/src/tag.rs @@ -0,0 +1,28 @@ +// 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; + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] +pub struct TagId(String); + +#[derive(Debug)] +pub struct Tag { + pub id: TagId, + pub windows: Vec, + // TODO: layout +} + +#[derive(Debug, Default)] +pub struct TagState { + pub tags: Vec, +} + +impl TagState { + pub fn new() -> Self { + Default::default() + } +} diff --git a/src/window.rs b/src/window.rs index 71d54e7..9c80bb7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,7 +18,6 @@ use crate::{ use self::window_state::{Float, WindowId, WindowState}; -pub mod tag; pub mod window_state; // TODO: maybe get rid of this and move the fn into resize_surface state because it's the only user @@ -51,6 +50,12 @@ impl State { .elements() .find(|window| window.wl_surface().map(|s| s == *surface).unwrap_or(false)) .cloned() + .or_else(|| { + self.windows + .iter() + .find(|&win| win.toplevel().wl_surface() == surface) + .cloned() + }) } /// Swap the positions and sizes of two windows. diff --git a/src/window/tag.rs b/src/window/tag.rs deleted file mode 100644 index f808aaa..0000000 --- a/src/window/tag.rs +++ /dev/null @@ -1,28 +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; - -use crate::{backend::Backend, state::State}; - -use super::window_state::WindowState; - -#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct Tag(String); - -impl Tag { - /// Returns all windows that have this tag. - pub fn windows(&self, state: &State) -> Vec { - state - .space - .elements() - .filter(|&window| { - WindowState::with_state(window, |win_state| win_state.tags.contains(self)) - }) - .cloned() - .collect() - } -} diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 5e521a0..c1a8fb2 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -14,11 +14,12 @@ use smithay::{ utils::{Logical, Point, Serial, Size}, }; -use super::tag::Tag; +use crate::tag::{Tag, TagId, TagState}; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct WindowId(u32); +// TODO: this probably doesn't need to be atomic static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0); impl WindowId { @@ -35,7 +36,16 @@ 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, +} + +/// 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_state(window, |state| state.tags.contains(&tag.id))) + .collect() } /// The state of a window's resize operation.