mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-29 20:34:46 +01:00
Begin work on tags
This commit is contained in:
parent
b39cc1d29a
commit
dc9b8e4ffc
17 changed files with 242 additions and 52 deletions
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ local pinnacle = {
|
|||
client = require("client"),
|
||||
---Process spawning
|
||||
process = require("process"),
|
||||
---Tag management
|
||||
tag = require("tag"),
|
||||
}
|
||||
|
||||
---Quit Pinnacle.
|
||||
|
|
48
api/lua/tag.lua
Normal file
48
api/lua/tag.lua
Normal file
|
@ -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
|
|
@ -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<u32>,
|
||||
},
|
||||
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<TagId>,
|
||||
},
|
||||
RemoveTags {
|
||||
tags: Vec<TagId>,
|
||||
},
|
||||
|
||||
// Process management
|
||||
/// Spawn a program with an optional callback.
|
||||
Spawn {
|
||||
|
|
|
@ -387,7 +387,9 @@ pub fn run_winit() -> Result<(), Box<dyn Error>> {
|
|||
event_loop.run(
|
||||
Some(Duration::from_millis(6)),
|
||||
&mut CalloopData { display, state },
|
||||
|_data| {},
|
||||
|_data| {
|
||||
// println!("{}", _data.state.space.elements().count());
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -17,6 +17,7 @@ impl FocusState {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
/// Get the currently focused window.
|
||||
pub fn current_focus(&mut self) -> Option<Window> {
|
||||
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);
|
||||
|
|
|
@ -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<B: Backend> CompositorHandler for State<B> {
|
|||
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<B: Backend> XdgShellHandler for State<B> {
|
|||
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<B: Backend> XdgShellHandler for State<B> {
|
|||
SERIAL_COUNTER.next_serial(),
|
||||
);
|
||||
});
|
||||
|
||||
let windows: Vec<Window> = self.space.elements().cloned().collect();
|
||||
|
||||
self.loop_handle.insert_idle(|data| {
|
||||
|
@ -345,15 +359,18 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
|
||||
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<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
}
|
||||
|
||||
// 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!(@<B: Backend> State<B>);
|
||||
|
|
|
@ -234,10 +234,15 @@ impl<B: Backend> State<B> {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -27,6 +27,7 @@ mod output;
|
|||
mod pointer;
|
||||
mod render;
|
||||
mod state;
|
||||
mod tag;
|
||||
mod window;
|
||||
mod xdg;
|
||||
|
||||
|
|
|
@ -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<Tag>,
|
||||
pub focused_tags: Vec<TagId>,
|
||||
}
|
||||
|
||||
impl OutputState {
|
||||
|
@ -22,7 +22,7 @@ impl OutputState {
|
|||
{
|
||||
output
|
||||
.user_data()
|
||||
.insert_if_missing(|| RefCell::<Self>::default);
|
||||
.insert_if_missing(RefCell::<Self>::default);
|
||||
|
||||
let state = output
|
||||
.user_data()
|
||||
|
|
63
src/state.rs
63
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<B: Backend> {
|
|||
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<f64, Logical>,
|
||||
pub windows: Vec<Window>,
|
||||
|
||||
pub async_scheduler: Scheduler<()>,
|
||||
}
|
||||
|
@ -181,8 +183,6 @@ impl<B: Backend> State<B> {
|
|||
} => {
|
||||
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<B: Backend> State<B> {
|
|||
});
|
||||
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::<Vec<_>>() {
|
||||
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::<Vec<_>>()
|
||||
|
||||
});
|
||||
|
||||
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<B: Backend> State<B> {
|
|||
input_state: InputState::new(),
|
||||
api_state: ApiState::new(),
|
||||
focus_state: FocusState::new(),
|
||||
tag_state: TagState::new(),
|
||||
|
||||
seat,
|
||||
|
||||
|
@ -374,6 +426,8 @@ impl<B: Backend> State<B> {
|
|||
popup_manager: PopupManager::default(),
|
||||
|
||||
async_scheduler: sched,
|
||||
|
||||
windows: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -412,7 +466,6 @@ impl<B: Backend> State<B> {
|
|||
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)]
|
||||
|
|
28
src/tag.rs
Normal file
28
src/tag.rs
Normal file
|
@ -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<Window>,
|
||||
// TODO: layout
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TagState {
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
|
||||
impl TagState {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -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<B: Backend> State<B> {
|
|||
.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.
|
||||
|
|
|
@ -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<B: Backend>(&self, state: &State<B>) -> Vec<Window> {
|
||||
state
|
||||
.space
|
||||
.elements()
|
||||
.filter(|&window| {
|
||||
WindowState::with_state(window, |win_state| win_state.tags.contains(self))
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -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<Tag>,
|
||||
pub tags: Vec<TagId>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
Loading…
Add table
Reference in a new issue