mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Add more window methods to API
This commit is contained in:
parent
85284f72ad
commit
f2b54be2fc
9 changed files with 574 additions and 266 deletions
|
@ -41,7 +41,12 @@ require("pinnacle").setup(function(pinnacle)
|
|||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, window.toggle_floating)
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, function()
|
||||
local win = window.get_focused()
|
||||
if win ~= nil then
|
||||
win:toggle_floating()
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key }, keys.Return, function()
|
||||
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
||||
|
@ -59,6 +64,18 @@ require("pinnacle").setup(function(pinnacle)
|
|||
process.spawn("nautilus")
|
||||
end)
|
||||
|
||||
-- Just testing stuff
|
||||
input.keybind({ mod_key }, keys.h, function()
|
||||
local win = window.get_focused()
|
||||
if win ~= nil then
|
||||
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil"))
|
||||
print("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
|
||||
print("class: " .. (win:class() or "nil"))
|
||||
print("title: " .. (win:title() or "nil"))
|
||||
print("float: " .. tostring(win:floating()))
|
||||
end
|
||||
end)
|
||||
|
||||
-- Tags ---------------------------------------------------------------------------
|
||||
|
||||
output.connect_for_all(function(op)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
--Windows
|
||||
---@field CloseWindow { window_id: integer }
|
||||
---@field ToggleFloating { window_id: integer }
|
||||
---@field SetWindowSize { window_id: integer, size: integer[] }
|
||||
---@field SetWindowSize { window_id: integer, width: integer?, height: integer? }
|
||||
---@field MoveWindowToTag { window_id: integer, tag_id: string }
|
||||
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
|
||||
--
|
||||
|
@ -35,6 +35,11 @@
|
|||
--Windows
|
||||
---@field GetWindowByAppId { app_id: string }
|
||||
---@field GetWindowByTitle { title: string }
|
||||
---@field GetWindowSize { window_id: WindowId }
|
||||
---@field GetWindowLocation { window_id: WindowId }
|
||||
---@field GetWindowFLoating { window_id: WindowId }
|
||||
---@field GetWindowClass { window_id: WindowId }
|
||||
---@field GetWindowTitle { window_id: WindowId }
|
||||
--Outputs
|
||||
---@field GetOutputByName { output_name: OutputName }
|
||||
---@field GetOutputsByModel { model: string }
|
||||
|
@ -58,9 +63,18 @@
|
|||
---@alias OutputName string
|
||||
|
||||
---@class RequestResponse
|
||||
--Windows
|
||||
---@field Window { window_id: WindowId|nil }
|
||||
---@field Windows { window_ids: WindowId[] }
|
||||
---@field WindowSize { size: (integer[])? }
|
||||
---@field WindowLocation { loc: (integer[])? }
|
||||
---@field WindowClass { class: string? }
|
||||
---@field WindowTitle { title: string? }
|
||||
---@field WindowFloating { floating: boolean? }
|
||||
--Outputs
|
||||
---@field Output { output_name: OutputName? }
|
||||
---@field Outputs { output_names: OutputName[] }
|
||||
--Tags
|
||||
---@field Tags { tag_ids: TagId[] }
|
||||
---@field TagActive { active: boolean }
|
||||
---@field TagName { name: string }
|
||||
|
|
|
@ -54,7 +54,7 @@ local output = {}
|
|||
---print(monitor.name) -- should print `DP-1`
|
||||
---```
|
||||
---@param name string The name of the output.
|
||||
---@return Output|nil
|
||||
---@return Output|nil output The output, or nil if none have the provided name.
|
||||
function output.get_by_name(name)
|
||||
SendRequest({
|
||||
GetOutputByName = {
|
||||
|
@ -64,10 +64,10 @@ function output.get_by_name(name)
|
|||
|
||||
local response = ReadMsg()
|
||||
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
local output_name = response.RequestResponse.response.Output.output_name
|
||||
|
||||
if output_names[1] ~= nil then
|
||||
return new_output({ name = output_names[1] })
|
||||
if output_name ~= nil then
|
||||
return new_output({ name = output_name })
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
@ -78,7 +78,7 @@ end
|
|||
---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.
|
||||
---@return Output[] outputs All outputs with this model.
|
||||
function output.get_by_model(model)
|
||||
SendRequest({
|
||||
GetOutputsByModel = {
|
||||
|
@ -103,7 +103,7 @@ end
|
|||
---
|
||||
---@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.
|
||||
---@return Output[] outputs All outputs with this resolution.
|
||||
function output.get_by_res(width, height)
|
||||
SendRequest({
|
||||
GetOutputsByRes = {
|
||||
|
@ -149,10 +149,10 @@ function output.get_focused()
|
|||
|
||||
local response = ReadMsg()
|
||||
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
local output_name = response.RequestResponse.response.Output.output_name
|
||||
|
||||
if output_names[1] ~= nil then
|
||||
return new_output({ name = output_names[1] })
|
||||
if output_name ~= nil then
|
||||
return new_output({ name = output_name })
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
|
|
@ -86,6 +86,7 @@ function pinnacle.setup(config_func)
|
|||
function SendMsg(data)
|
||||
local encoded = msgpack.encode(data)
|
||||
assert(encoded)
|
||||
-- print(encoded)
|
||||
local len = encoded:len()
|
||||
socket.send(socket_fd, string.pack("=I4", len))
|
||||
socket.send(socket_fd, encoded)
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
|
||||
---@class Window
|
||||
---@field private id integer The internal id of this window
|
||||
---@field private app_id string? The equivalent of an X11 window's class
|
||||
---@field private title string? The window's title
|
||||
---@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 floating boolean Whether the window is floating or not (tiled)
|
||||
local win = {}
|
||||
|
||||
---@param props Window
|
||||
|
@ -25,21 +20,32 @@ local function new_window(props)
|
|||
end
|
||||
|
||||
---Set a window's size.
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
---window.get_focused():set_size({ w = 500, h = 500 }) -- make the window square and 500 pixels wide/tall
|
||||
---window.get_focused():set_size({ h = 300 }) -- keep the window's width but make it 300 pixels tall
|
||||
---window.get_focused():set_size({}) -- do absolutely nothing useful
|
||||
---```
|
||||
---@param size { w: integer?, h: integer? }
|
||||
function win:set_size(size)
|
||||
self.size = {
|
||||
w = size.w or self.size.w,
|
||||
h = size.h or self.size.h,
|
||||
}
|
||||
SendMsg({
|
||||
SetWindowSize = {
|
||||
window_id = self.id,
|
||||
size = { self.size.w, self.size.h },
|
||||
width = size.w,
|
||||
height = size.h,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Move a window to a tag, removing all other ones.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window on tags 1, 2, 3, and 4...
|
||||
---window.get_focused():move_to_tag("5")
|
||||
----- ...will make the window only appear on tag 5.
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
function win:move_to_tag(name)
|
||||
SendMsg({
|
||||
|
@ -51,6 +57,15 @@ function win:move_to_tag(name)
|
|||
end
|
||||
|
||||
---Toggle the specified tag for this window.
|
||||
---
|
||||
---Note: toggling off all tags currently makes a window not response to layouting.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window only on tag 1...
|
||||
---window.get_focused():toggle_tag("2")
|
||||
----- ...will also make the window appear on tag 2.
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
function win:toggle_tag(name)
|
||||
SendMsg({
|
||||
|
@ -62,6 +77,14 @@ function win:toggle_tag(name)
|
|||
end
|
||||
|
||||
---Close this window.
|
||||
---
|
||||
---This only sends a close *event* to the window and is the same as just clicking the X button in the titlebar.
|
||||
---This will trigger save prompts in applications like GIMP.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---window.get_focused():close() -- close the currently focused window
|
||||
---```
|
||||
function win:close()
|
||||
SendMsg({
|
||||
CloseWindow = {
|
||||
|
@ -71,6 +94,11 @@ function win:close()
|
|||
end
|
||||
|
||||
---Toggle this window's floating status.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---window.get_focused():toggle_floating() -- toggles the focused window between tiled and floating
|
||||
---```
|
||||
function win:toggle_floating()
|
||||
SendMsg({
|
||||
ToggleFloating = {
|
||||
|
@ -80,9 +108,127 @@ function win:toggle_floating()
|
|||
end
|
||||
|
||||
---Get a window's size.
|
||||
---@return { w: integer, h: integer }
|
||||
function win:get_size()
|
||||
return self.size
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With a 4K monitor, given a focused fullscreen window...
|
||||
---local size = window.get_focused():size()
|
||||
----- ...should have size equal to `{ w = 3840, h = 2160 }`.
|
||||
---```
|
||||
---@return { w: integer, h: integer }|nil size The size of the window, or nil if it doesn't exist.
|
||||
function win:size()
|
||||
SendRequest({
|
||||
GetWindowSize = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local size = response.RequestResponse.response.WindowSize.size
|
||||
if size == nil then
|
||||
return nil
|
||||
else
|
||||
return {
|
||||
w = size[1],
|
||||
h = size[2],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Get this window's location in the global space.
|
||||
---
|
||||
---Think of your monitors as being laid out on a big sheet.
|
||||
---The top left of the sheet if you trim it down is (0, 0).
|
||||
---The location of this window is relative to that point.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With two 1080p monitors side by side and set up as such,
|
||||
----- if a window is fullscreen on the right one...
|
||||
---local loc = that_window:loc()
|
||||
----- ...should have loc equal to `{ x = 1920, y = 0 }`.
|
||||
---```
|
||||
---@return { x: integer, y: integer }|nil loc The location of the window, or nil if it's not on-screen or alive.
|
||||
function win:loc()
|
||||
SendRequest({
|
||||
GetWindowLocation = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local loc = response.RequestResponse.response.WindowLocation.loc
|
||||
if loc == nil then
|
||||
return nil
|
||||
else
|
||||
return {
|
||||
x = loc[1],
|
||||
y = loc[2],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Get this window's class. This is usually the name of the application.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---print(window.get_focused():class())
|
||||
----- ...should print "Alacritty".
|
||||
---```
|
||||
---@return string|nil class This window's class, or nil if it doesn't exist.
|
||||
function win:class()
|
||||
SendRequest({
|
||||
GetWindowClass = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local class = response.RequestResponse.response.WindowClass.class
|
||||
return class
|
||||
end
|
||||
|
||||
---Get this window's title.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---print(window.get_focused():title())
|
||||
----- ...should print the directory Alacritty is in or what it's running (what's in its title bar).
|
||||
---```
|
||||
---@return string|nil title This window's title, or nil if it doesn't exist.
|
||||
function win:title()
|
||||
SendRequest({
|
||||
GetWindowTitle = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local title = response.RequestResponse.response.WindowTitle.title
|
||||
return title
|
||||
end
|
||||
|
||||
---Get this window's floating status.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused and floating...
|
||||
---print(tostring(window.get_focused():floating()))
|
||||
----- ...should print "true".
|
||||
---```
|
||||
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||
function win:floating()
|
||||
SendRequest({
|
||||
GetWindowFloating = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local floating = response.RequestResponse.response.WindowFloating.floating
|
||||
return floating
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
|
|
@ -33,7 +33,10 @@ pub enum Msg {
|
|||
},
|
||||
SetWindowSize {
|
||||
window_id: WindowId,
|
||||
size: (i32, i32),
|
||||
#[serde(default)]
|
||||
width: Option<i32>,
|
||||
#[serde(default)]
|
||||
height: Option<i32>,
|
||||
},
|
||||
MoveWindowToTag {
|
||||
window_id: WindowId,
|
||||
|
@ -96,14 +99,22 @@ 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 {
|
||||
// Windows
|
||||
GetWindowByAppId { app_id: String },
|
||||
GetWindowByTitle { title: String },
|
||||
GetWindowByFocus,
|
||||
GetAllWindows,
|
||||
GetWindowSize { window_id: WindowId },
|
||||
GetWindowLocation { window_id: WindowId },
|
||||
GetWindowFloating { window_id: WindowId },
|
||||
GetWindowClass { window_id: WindowId },
|
||||
GetWindowTitle { window_id: WindowId },
|
||||
// Outputs
|
||||
GetOutputByName { output_name: String },
|
||||
GetOutputsByModel { model: String },
|
||||
GetOutputsByRes { res: (u32, u32) },
|
||||
GetOutputByFocus,
|
||||
// Tags
|
||||
GetTagsByOutput { output_name: String },
|
||||
GetTagActive { tag_id: TagId },
|
||||
GetTagName { tag_id: TagId },
|
||||
|
@ -187,6 +198,12 @@ pub enum Args {
|
|||
pub enum RequestResponse {
|
||||
Window { window_id: Option<WindowId> },
|
||||
Windows { window_ids: Vec<WindowId> },
|
||||
WindowSize { size: Option<(i32, i32)> },
|
||||
WindowLocation { loc: Option<(i32, i32)> },
|
||||
WindowClass { class: Option<String> },
|
||||
WindowTitle { title: Option<String> },
|
||||
WindowFloating { floating: Option<bool> },
|
||||
Output { output_name: Option<String> },
|
||||
Outputs { output_names: Vec<String> },
|
||||
Tags { tag_ids: Vec<TagId> },
|
||||
TagActive { active: bool },
|
||||
|
|
|
@ -159,7 +159,8 @@ impl Layout {
|
|||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
let WindowResizeState::Requested(_, loc) =
|
||||
state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
|
@ -271,7 +272,8 @@ impl Layout {
|
|||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
let WindowResizeState::Requested(_, loc) =
|
||||
state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
|
|
573
src/state.rs
573
src/state.rs
|
@ -55,7 +55,7 @@ use smithay::{
|
|||
dmabuf::DmabufFeedback,
|
||||
fractional_scale::FractionalScaleManagerState,
|
||||
output::OutputManagerState,
|
||||
shell::xdg::XdgShellState,
|
||||
shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
|
||||
shm::ShmState,
|
||||
socket::ListeningSocketSource,
|
||||
viewporter::ViewporterState,
|
||||
|
@ -119,20 +119,12 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
Msg::SetMousebind { button: _ } => todo!(),
|
||||
Msg::CloseWindow { window_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
window.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
Msg::ToggleFloating { window_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| state.id == window_id)).cloned()
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
crate::window::toggle_floating(self, &window);
|
||||
}
|
||||
}
|
||||
|
@ -144,23 +136,30 @@ impl<B: Backend> State<B> {
|
|||
self.handle_spawn(command, callback_id);
|
||||
}
|
||||
|
||||
Msg::SetWindowSize { window_id, size } => {
|
||||
let Some(window) = self.space.elements().find(|&win| {
|
||||
win.with_state( |state| state.id == window_id)
|
||||
}) else { return; };
|
||||
Msg::SetWindowSize {
|
||||
window_id,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
|
||||
// TODO: tiled vs floating
|
||||
let window_size = window.geometry().size;
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size.into());
|
||||
// INFO: calling window.geometry() in with_pending_state
|
||||
// | will hang the compositor
|
||||
state.size = Some(
|
||||
(
|
||||
width.unwrap_or(window_size.w),
|
||||
height.unwrap_or(window_size.h),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
window.toplevel().send_pending_configure();
|
||||
}
|
||||
Msg::MoveWindowToTag { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
window.with_state(|state| {
|
||||
self.focus_state
|
||||
.focused_output
|
||||
|
@ -173,17 +172,12 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
});
|
||||
});
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
self.re_layout(&output);
|
||||
}
|
||||
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
window.with_state(|state| {
|
||||
self.focus_state
|
||||
.focused_output
|
||||
|
@ -205,14 +199,21 @@ impl<B: Backend> State<B> {
|
|||
self.re_layout(&output);
|
||||
}
|
||||
}
|
||||
Msg::ToggleTag { output_name, tag_name } => {
|
||||
Msg::ToggleTag {
|
||||
output_name,
|
||||
tag_name,
|
||||
} => {
|
||||
tracing::debug!("ToggleTag");
|
||||
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
|
@ -220,10 +221,16 @@ impl<B: Backend> State<B> {
|
|||
self.re_layout(&output);
|
||||
}
|
||||
}
|
||||
Msg::SwitchToTag { output_name, tag_name } => {
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
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
|
||||
|
@ -233,7 +240,11 @@ impl<B: Backend> State<B> {
|
|||
tag.set_active(false);
|
||||
}
|
||||
|
||||
let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) else {
|
||||
let Some(tag) = state
|
||||
.tags
|
||||
.iter_mut()
|
||||
.find(|tag| tag.name() == tag_name)
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
tag.set_active(true);
|
||||
|
@ -252,21 +263,25 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
}
|
||||
// TODO: add output
|
||||
Msg::AddTags { output_name, tag_names } => {
|
||||
Msg::AddTags {
|
||||
output_name,
|
||||
tag_names,
|
||||
} => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
{
|
||||
output.with_state(|state| {
|
||||
state
|
||||
.tags
|
||||
.extend(tag_names.iter().cloned().map(Tag::new));
|
||||
state.tags.extend(tag_names.iter().cloned().map(Tag::new));
|
||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||
});
|
||||
}
|
||||
}
|
||||
Msg::RemoveTags { output_name, tag_names } => {
|
||||
Msg::RemoveTags {
|
||||
output_name,
|
||||
tag_names,
|
||||
} => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
|
@ -277,12 +292,20 @@ impl<B: Backend> State<B> {
|
|||
});
|
||||
}
|
||||
}
|
||||
Msg::SetLayout { output_name, tag_name, layout } => {
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
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) {
|
||||
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name)
|
||||
{
|
||||
tag.set_layout(layout);
|
||||
}
|
||||
});
|
||||
|
@ -317,194 +340,258 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
|
||||
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 => {
|
||||
match self.focus_state.current_focus() {
|
||||
Some(current_focus) => {
|
||||
let window_id =
|
||||
current_focus.with_state(|state| state.id);
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window_id: Some(window_id) },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
},
|
||||
None => {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window_id: None },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
},
|
||||
}
|
||||
}
|
||||
Request::GetAllWindows => {
|
||||
let window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|win| {
|
||||
win.with_state(|state| state.id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.handle_request(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: figure out what to do if error
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Windows {
|
||||
window_ids,
|
||||
},
|
||||
fn handle_request(&mut self, 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 => match self.focus_state.current_focus() {
|
||||
Some(current_focus) => {
|
||||
let window_id = current_focus.with_state(|state| state.id);
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window {
|
||||
window_id: Some(window_id),
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetOutputByName { output_name } => {
|
||||
// TODO: name better
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
.map(|output| output.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs {
|
||||
output_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::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: 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::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetOutputByFocus => {
|
||||
let names = self
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.map(|output| output.name())
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetTagsByOutput { output_name } => {
|
||||
let output = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == output_name);
|
||||
if let Some(output) = output {
|
||||
let tag_ids = output.with_state(|state| {
|
||||
state.tags
|
||||
.iter()
|
||||
.map(|tag| tag.id())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Tags { tag_ids }
|
||||
}).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();
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
None => {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window_id: None },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
},
|
||||
Request::GetAllWindows => {
|
||||
let window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|win| win.with_state(|state| state.id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// FIXME: figure out what to do if error
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Windows { window_ids },
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetWindowSize { window_id } => {
|
||||
let size = window_id
|
||||
.window(self)
|
||||
.map(|win| (win.geometry().size.w, win.geometry().size.h));
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::WindowSize { size },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetWindowLocation { window_id } => {
|
||||
let loc = window_id
|
||||
.window(self)
|
||||
.and_then(|win| self.space.element_location(&win))
|
||||
.map(|loc| (loc.x, loc.y));
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::WindowLocation { loc },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetWindowClass { window_id } => {
|
||||
let class = window_id.window(self).and_then(|win| {
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||
.lock()
|
||||
.expect("failed to acquire lock");
|
||||
lock.app_id.clone()
|
||||
})
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::WindowClass { class },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetWindowTitle { window_id } => {
|
||||
let title = window_id.window(self).and_then(|win| {
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||
.lock()
|
||||
.expect("failed to acquire lock");
|
||||
lock.title.clone()
|
||||
})
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::WindowTitle { title },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetWindowFloating { window_id } => {
|
||||
let floating = window_id
|
||||
.window(self)
|
||||
.map(|win| win.with_state(|state| state.floating.is_floating()));
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::WindowFloating { floating },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputByName { output_name } => {
|
||||
// TODO: name better
|
||||
let name = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
.map(|output| output.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Output { output_name: name },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputsByModel { model } => {
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter(|output| output.physical_properties().model == model)
|
||||
.map(|output| output.name())
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs {
|
||||
output_names: names,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
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::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs {
|
||||
output_names: names,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputByFocus => {
|
||||
let name = self
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.map(|output| output.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Output { output_name: name },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetTagsByOutput { output_name } => {
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name);
|
||||
if let Some(output) = output {
|
||||
let tag_ids = output.with_state(|state| {
|
||||
state.tags.iter().map(|tag| tag.id()).collect::<Vec<_>>()
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Tags { tag_ids },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
}
|
||||
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(),
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
}
|
||||
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() },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,9 +744,19 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
let (render, do_not_render) = output.with_state(|state| {
|
||||
let first_tag = state.focused_tags().next();
|
||||
if let Some(first_tag) = first_tag {
|
||||
|
@ -716,13 +813,15 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
/// Schedule something to be done when windows have finished committing and have become
|
||||
/// idle.
|
||||
pub fn schedule_on_commit<F, B: Backend>(data: &mut CalloopData<B>, windows: Vec<Window>, on_commit: F)
|
||||
where
|
||||
pub fn schedule_on_commit<F, B: Backend>(
|
||||
data: &mut CalloopData<B>,
|
||||
windows: Vec<Window>,
|
||||
on_commit: F,
|
||||
) where
|
||||
F: FnOnce(&mut CalloopData<B>) + 'static,
|
||||
{
|
||||
for window in windows.iter() {
|
||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle))
|
||||
{
|
||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) {
|
||||
data.state.loop_handle.insert_idle(|data| {
|
||||
schedule_on_commit(data, windows, on_commit);
|
||||
});
|
||||
|
|
|
@ -15,18 +15,30 @@ use smithay::{
|
|||
utils::{Logical, Point, Serial, Size},
|
||||
};
|
||||
|
||||
use crate::{state::WithState, tag::Tag};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
tag::Tag,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, 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 {
|
||||
pub fn next() -> Self {
|
||||
Self(WINDOW_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Get the window that has this WindowId.
|
||||
pub fn window<B: Backend>(&self, state: &State<B>) -> Option<Window> {
|
||||
state
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| &state.id == self))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowState {
|
||||
|
|
Loading…
Reference in a new issue