Add moving windows to tags and toggling tags on windows

This commit is contained in:
Seaotatop 2023-07-02 10:26:07 -05:00
parent ebe2313e2d
commit 8fcf86b886
9 changed files with 253 additions and 136 deletions

View file

@ -12,7 +12,7 @@ pcall(require, "luarocks.loader")
-- Neovim users be like: -- Neovim users be like:
require("pinnacle").setup(function(pinnacle) require("pinnacle").setup(function(pinnacle)
local input = pinnacle.input -- Key and mouse binds local input = pinnacle.input -- Key and mouse binds
local client = pinnacle.client -- Window management local window = pinnacle.window -- Window management
local process = pinnacle.process -- Process spawning local process = pinnacle.process -- Process spawning
local tag = pinnacle.tag -- Tag management local tag = pinnacle.tag -- Tag management
@ -20,62 +20,90 @@ require("pinnacle").setup(function(pinnacle)
-- Support for just putting in a string of a key is intended. -- Support for just putting in a string of a key is intended.
local keys = input.keys local keys = input.keys
---@type Modifier
local mod_key = "Ctrl" -- This is set to `Ctrl` instead of `Super` to not conflict with your WM/DE keybinds
-- ^ Add type annotations for that sweet, sweet autocomplete
local terminal = "alacritty"
-- Keybinds ---------------------------------------------------------------------- -- Keybinds ----------------------------------------------------------------------
input.keybind({ "Ctrl", "Alt" }, keys.q, pinnacle.quit) input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
input.keybind({ "Ctrl", "Alt" }, keys.c, client.close_window) input.keybind({ mod_key, "Alt" }, keys.c, window.close_window)
input.keybind({ "Ctrl", "Alt" }, keys.space, client.toggle_floating) input.keybind({ mod_key, "Alt" }, keys.space, window.toggle_floating)
input.keybind({ "Ctrl" }, keys.Return, function() input.keybind({ mod_key }, keys.Return, function()
process.spawn("alacritty", function(stdout, stderr, exit_code, exit_msg) process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
-- do something with the output here; remember to check for nil! -- do something with the output here
end) end)
end) end)
input.keybind({ "Ctrl" }, keys.KEY_1, function()
process.spawn("kitty")
end)
input.keybind({ "Ctrl" }, keys.KEY_2, function()
process.spawn("foot")
end)
input.keybind({ "Ctrl" }, keys.KEY_3, function()
process.spawn("nautilus")
end)
-- Tags --------------------------------------------------------------------------- -- Tags ---------------------------------------------------------------------------
tag.add("1", "2", "3", "4", "5") tag.add("1", "2", "3", "4", "5")
tag.toggle("1") tag.toggle("1")
input.keybind({ "Ctrl", "Shift" }, keys.KEY_1, function() input.keybind({ mod_key }, keys.KEY_1, function()
tag.switch_to("1")
end)
input.keybind({ mod_key }, keys.KEY_2, function()
tag.switch_to("2")
end)
input.keybind({ mod_key }, keys.KEY_3, function()
tag.switch_to("3")
end)
input.keybind({ mod_key }, keys.KEY_4, function()
tag.switch_to("4")
end)
input.keybind({ mod_key }, keys.KEY_5, function()
tag.switch_to("5")
end)
input.keybind({ mod_key, "Shift" }, keys.KEY_1, function()
tag.toggle("1") tag.toggle("1")
end) end)
input.keybind({ "Ctrl", "Shift" }, keys.KEY_2, function() input.keybind({ mod_key, "Shift" }, keys.KEY_2, function()
tag.toggle("2") tag.toggle("2")
end) end)
input.keybind({ "Ctrl", "Shift" }, keys.KEY_3, function() input.keybind({ mod_key, "Shift" }, keys.KEY_3, function()
tag.toggle("3") tag.toggle("3")
end) end)
input.keybind({ "Ctrl", "Shift" }, keys.KEY_4, function() input.keybind({ mod_key, "Shift" }, keys.KEY_4, function()
tag.toggle("4") tag.toggle("4")
end) end)
input.keybind({ "Ctrl", "Shift" }, keys.KEY_5, function() input.keybind({ mod_key, "Shift" }, keys.KEY_5, function()
tag.toggle("5") tag.toggle("5")
end) end)
input.keybind({ "Ctrl", "Alt", "Shift" }, keys.KEY_1, function() input.keybind({ mod_key, "Alt" }, keys.KEY_1, function()
tag.switch_to("1") window.get_focused():move_to_tag("1")
end) end)
input.keybind({ "Ctrl", "Alt", "Shift" }, keys.KEY_2, function() input.keybind({ mod_key, "Alt" }, keys.KEY_2, function()
tag.switch_to("2") window.get_focused():move_to_tag("2")
end) end)
input.keybind({ "Ctrl", "Alt", "Shift" }, keys.KEY_3, function() input.keybind({ mod_key, "Alt" }, keys.KEY_3, function()
tag.switch_to("3") window.get_focused():move_to_tag("3")
end) end)
input.keybind({ "Ctrl", "Alt", "Shift" }, keys.KEY_4, function() input.keybind({ mod_key, "Alt" }, keys.KEY_4, function()
tag.switch_to("4") window.get_focused():move_to_tag("4")
end) end)
input.keybind({ "Ctrl", "Alt", "Shift" }, keys.KEY_5, function() input.keybind({ mod_key, "Alt" }, keys.KEY_5, function()
tag.switch_to("5") window.get_focused():move_to_tag("5")
end)
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_1, function()
window.get_focused():toggle_tag("1")
end)
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_2, function()
window.get_focused():toggle_tag("2")
end)
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_3, function()
window.get_focused():toggle_tag("3")
end)
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_4, function()
window.get_focused():toggle_tag("4")
end)
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_5, function()
window.get_focused():toggle_tag("5")
end) end)
end) end)

View file

@ -8,9 +8,9 @@ local input = {
keys = require("keys"), keys = require("keys"),
} }
---Set a keybind. If called on an already existing keybind, it gets replaced. ---Set a keybind. If called with an already existing keybind, it gets replaced.
---@param key Keys The key for the keybind. NOTE: uppercase and lowercase characters are considered different. ---@param key Keys The key for the keybind.
---@param modifiers Modifiers[] Which modifiers need to be pressed for the keybind to trigger. ---@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 run.
function input.keybind(modifiers, key, action) function input.keybind(modifiers, key, action)
table.insert(CallbackTable, action) table.insert(CallbackTable, action)

View file

@ -4,7 +4,7 @@
-- --
-- SPDX-License-Identifier: MPL-2.0 -- SPDX-License-Identifier: MPL-2.0
---@alias Modifiers "Alt" | "Ctrl" | "Shift" | "Super" ---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super"
---@enum Keys ---@enum Keys
local M = { local M = {

View file

@ -9,9 +9,13 @@
---@class _Msg ---@class _Msg
---@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer } ---@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer }
---@field SetMousebind { button: integer } ---@field SetMousebind { button: integer }
--Windows
---@field CloseWindow { client_id: integer? } ---@field CloseWindow { client_id: integer? }
---@field ToggleFloating { client_id: integer? } ---@field ToggleFloating { client_id: integer? }
---@field SetWindowSize { window_id: integer, size: { w: integer, h: integer } } ---@field SetWindowSize { window_id: integer, size: { w: integer, h: integer } }
---@field MoveWindowToTag { window_id: integer, tag_id: string }
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
--
---@field Spawn { command: string[], callback_id: integer? } ---@field Spawn { command: string[], callback_id: integer? }
---@field Request Request ---@field Request Request
--Tags --Tags

View file

@ -51,7 +51,7 @@ local pinnacle = {
---Key and mouse binds ---Key and mouse binds
input = require("input"), input = require("input"),
---Window management ---Window management
client = require("client"), window = require("window"),
---Process spawning ---Process spawning
process = require("process"), process = require("process"),
---Tag management ---Tag management

View file

@ -8,6 +8,8 @@ local tag = {}
---Add tags. ---Add tags.
--- ---
---If you need to add the strings in a table, use `tag.add_table` instead.
---
---# Example ---# Example
--- ---
---```lua ---```lua
@ -25,7 +27,7 @@ function tag.add(...)
}) })
end end
---Like `tag.add(...)`, but with a table of strings instead. ---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. ---@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(tags)
SendMsg({ SendMsg({

View file

@ -11,13 +11,13 @@
---@field private size { w: integer, h: integer } The size of the window ---@field private size { w: integer, h: integer } The size of the window
---@field private location { x: integer, y: integer } The location of the window ---@field private location { x: integer, y: integer } The location of the window
---@field private floating boolean Whether the window is floating or not (tiled) ---@field private floating boolean Whether the window is floating or not (tiled)
local window = {} 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 { id: integer, app_id: string?, title: string?, size: { w: integer, h: integer }, location: { x: integer, y: integer }, floating: boolean }
---@return Window ---@return Window
local function new_window(props) local function new_window(props)
-- Copy functions over -- Copy functions over
for k, v in pairs(window) do for k, v in pairs(win) do
props[k] = v props[k] = v
end end
@ -26,7 +26,7 @@ end
---Set a window's size. ---Set a window's size.
---@param size { w: integer?, h: integer? } ---@param size { w: integer?, h: integer? }
function window:set_size(size) function win:set_size(size)
self.size = { self.size = {
w = size.w or self.size.w, w = size.w or self.size.w,
h = size.h or self.size.h, h = size.h or self.size.h,
@ -39,19 +39,41 @@ function window:set_size(size)
}) })
end end
---Move a window to a tag, removing all other ones.
---@param name string The name of the tag.
function win:move_to_tag(name)
SendMsg({
MoveWindowToTag = {
window_id = self.id,
tag_id = name,
},
})
end
---Toggle the specified tag for this window.
---@param name string The name of the tag.
function win:toggle_tag(name)
SendMsg({
ToggleTagOnWindow = {
window_id = self.id,
tag_id = name,
},
})
end
---Get a window's size. ---Get a window's size.
---@return { w: integer, h: integer } ---@return { w: integer, h: integer }
function window:get_size() function win:get_size()
return self.size return self.size
end end
------------------------------------------------------------------- -------------------------------------------------------------------
local client = {} local window = {}
---Close a window. ---Close a window.
---@param client_id integer? The id of the window you want closed, or nil to close the currently focused window, if any. ---@param client_id integer? The id of the window you want closed, or nil to close the currently focused window, if any.
function client.close_window(client_id) function window.close_window(client_id)
SendMsg({ SendMsg({
CloseWindow = { CloseWindow = {
client_id = client_id, client_id = client_id,
@ -61,7 +83,7 @@ end
---Toggle a window's floating status. ---Toggle a window's floating status.
---@param client_id integer? The id of the window you want to toggle, or nil to toggle the currently focused window, if any. ---@param client_id integer? The id of the window you want to toggle, or nil to toggle the currently focused window, if any.
function client.toggle_floating(client_id) function window.toggle_floating(client_id)
SendMsg({ SendMsg({
ToggleFloating = { ToggleFloating = {
client_id = client_id, client_id = client_id,
@ -69,39 +91,25 @@ function client.toggle_floating(client_id)
}) })
end end
---Get a window. ---Get a window by its app id (aka its X11 class).
---@param identifier { app_id: string } | { title: string } | "focus" A table with either the key app_id or title, depending if you want to get the window via its app_id or title, OR the string "focus" to get the currently focused window. ---@param app_id string The window's app id. For example, Alacritty's app id is "Alacritty".
---@return Window ---@return Window window -- TODO: nil
function client.get_window(identifier) function window.get_by_app_id(app_id)
local req_id = Requests:next() local req_id = Requests:next()
if type(identifier) == "string" then
SendRequest({ SendRequest({
GetWindowByFocus = { GetWindowByAppId = {
id = req_id, id = req_id,
}, app_id = app_id,
}) },
elseif identifier.app_id then })
SendRequest({
GetWindowByAppId = {
id = req_id,
app_id = identifier.app_id,
},
})
else
SendRequest({
GetWindowByTitle = {
id = req_id,
title = identifier.title,
},
})
end
local response = ReadMsg() local response = ReadMsg()
local props = response.RequestResponse.response.Window.window local props = response.RequestResponse.response.Window.window
---@type Window ---@type Window
local win = { local wind = {
id = props.id, id = props.id,
app_id = props.app_id or "", app_id = props.app_id or "",
title = props.title or "", title = props.title or "",
@ -116,12 +124,82 @@ function client.get_window(identifier)
floating = props.floating, floating = props.floating,
} }
return new_window(win) return new_window(wind)
end
---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,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.Window.window
---@type Window
local wind = {
id = props.id,
app_id = props.app_id or "",
title = props.title or "",
size = {
w = props.size[1],
h = props.size[2],
},
location = {
x = props.location[1],
y = props.location[2],
},
floating = props.floating,
}
return new_window(wind)
end
---Get the currently focused window.
---@return Window
function window.get_focused()
local req_id = Requests:next()
SendRequest({
GetWindowByFocus = {
id = req_id,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.Window.window
---@type Window
local wind = {
id = props.id,
app_id = props.app_id or "",
title = props.title or "",
size = {
w = props.size[1],
h = props.size[2],
},
location = {
x = props.location[1],
y = props.location[2],
},
floating = props.floating,
}
return new_window(wind)
end end
---Get all windows. ---Get all windows.
---@return Window[] ---@return Window[]
function client.get_windows() function window.get_windows()
SendRequest({ SendRequest({
GetAllWindows = { GetAllWindows = {
id = Requests:next(), id = Requests:next(),
@ -152,6 +230,4 @@ function client.get_windows()
return windows return windows
end end
-- local win = client.get_window("focus") return window
return client

View file

@ -40,8 +40,12 @@ pub enum Msg {
window_id: WindowId, window_id: WindowId,
size: (i32, i32), size: (i32, i32),
}, },
MoveToTag { MoveWindowToTag {
// TODO: window_id: WindowId,
tag_id: TagId,
},
ToggleTagOnWindow {
window_id: WindowId,
tag_id: TagId, tag_id: TagId,
}, },

View file

@ -195,9 +195,34 @@ impl<B: Backend> State<B> {
}); });
window.toplevel().send_pending_configure(); window.toplevel().send_pending_configure();
} }
Msg::MoveToTag { tag_id } => todo!(), Msg::MoveWindowToTag { window_id, tag_id } => {
if let Some(window) = data.state.windows.iter().find(|&win| {
WindowState::with_state(win, |state| state.id == window_id)
}) {
WindowState::with_state(window, |state| {
state.tags = vec![tag_id.clone()];
});
}
data.state.re_layout();
},
Msg::ToggleTagOnWindow { window_id, tag_id } => {
if let Some(window) = data.state.windows.iter().find(|&win| {
WindowState::with_state(win, |state| state.id == window_id)
}) {
WindowState::with_state(window, |state| {
if state.tags.contains(&tag_id) {
state.tags.retain(|id| id != &tag_id);
} else {
state.tags.push(tag_id.clone());
}
});
data.state.re_layout();
}
},
Msg::ToggleTag { tag_id } => { Msg::ToggleTag { tag_id } => {
let windows = OutputState::with( OutputState::with(
data data
.state .state
.focus_state .focus_state
@ -215,40 +240,13 @@ impl<B: Backend> State<B> {
tracing::debug!("toggled tag {tag_id:?} on"); tracing::debug!("toggled tag {tag_id:?} on");
} }
} }
// 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.get(tag_id).unwrap_or(&false) {
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.get(tag_id).unwrap_or(&false) {
return true;
}
}
false
})
}).cloned().collect::<Vec<_>>()
} }
); );
tracing::info!("Laying out {} windows", windows.len()); data.state.re_layout();
Layout::master_stack(&mut data.state, windows, crate::layout::Direction::Left);
}, },
Msg::SwitchToTag { tag_id } => { Msg::SwitchToTag { tag_id } => {
let windows = OutputState::with(data OutputState::with(data
.state .state
.focus_state .focus_state
.focused_output .focused_output
@ -263,36 +261,10 @@ impl<B: Backend> State<B> {
} else { } else {
state.focused_tags.insert(tag_id.clone(), true); state.focused_tags.insert(tag_id.clone(), true);
} }
// TODO: extract into fn, same with the one up there
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.get(tag_id).unwrap_or(&false) {
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.get(tag_id).unwrap_or(&false) {
return true;
}
}
false
})
}).cloned().collect::<Vec<_>>()
} }
); );
Layout::master_stack(&mut data.state, windows, crate::layout::Direction::Left); data.state.re_layout();
} }
Msg::AddTags { tags } => { Msg::AddTags { tags } => {
data data
@ -640,6 +612,37 @@ impl<B: Backend> State<B> {
} }
} }
} }
pub fn re_layout(&mut self) {
let windows = OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| {
for window in self.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.get(tag_id).unwrap_or(&false) {
return true;
}
}
false
});
if !should_render {
self.space.unmap_elem(&window);
}
}
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).unwrap_or(&false) {
return true;
}
}
false
})
}).cloned().collect::<Vec<_>>()
});
Layout::master_stack(self, windows, crate::layout::Direction::Left);
}
} }
pub struct CalloopData<B: Backend> { pub struct CalloopData<B: Backend> {