Add more window methods to API

This commit is contained in:
Ottatop 2023-07-18 21:10:43 -05:00
parent 85284f72ad
commit f2b54be2fc
9 changed files with 574 additions and 266 deletions

View file

@ -41,7 +41,12 @@ require("pinnacle").setup(function(pinnacle)
end end
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() input.keybind({ mod_key }, keys.Return, function()
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg) process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
@ -59,6 +64,18 @@ require("pinnacle").setup(function(pinnacle)
process.spawn("nautilus") process.spawn("nautilus")
end) 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 --------------------------------------------------------------------------- -- Tags ---------------------------------------------------------------------------
output.connect_for_all(function(op) output.connect_for_all(function(op)

View file

@ -12,7 +12,7 @@
--Windows --Windows
---@field CloseWindow { window_id: integer } ---@field CloseWindow { window_id: integer }
---@field ToggleFloating { 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 MoveWindowToTag { window_id: integer, tag_id: string }
---@field ToggleTagOnWindow { window_id: integer, tag_id: string } ---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
-- --
@ -35,6 +35,11 @@
--Windows --Windows
---@field GetWindowByAppId { app_id: string } ---@field GetWindowByAppId { app_id: string }
---@field GetWindowByTitle { title: 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 --Outputs
---@field GetOutputByName { output_name: OutputName } ---@field GetOutputByName { output_name: OutputName }
---@field GetOutputsByModel { model: string } ---@field GetOutputsByModel { model: string }
@ -58,9 +63,18 @@
---@alias OutputName string ---@alias OutputName string
---@class RequestResponse ---@class RequestResponse
--Windows
---@field Window { window_id: WindowId|nil } ---@field Window { window_id: WindowId|nil }
---@field Windows { window_ids: WindowId[] } ---@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[] } ---@field Outputs { output_names: OutputName[] }
--Tags
---@field Tags { tag_ids: TagId[] } ---@field Tags { tag_ids: TagId[] }
---@field TagActive { active: boolean } ---@field TagActive { active: boolean }
---@field TagName { name: string } ---@field TagName { name: string }

View file

@ -54,7 +54,7 @@ local output = {}
---print(monitor.name) -- should print `DP-1` ---print(monitor.name) -- should print `DP-1`
---``` ---```
---@param name string The name of the output. ---@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) function output.get_by_name(name)
SendRequest({ SendRequest({
GetOutputByName = { GetOutputByName = {
@ -64,10 +64,10 @@ function output.get_by_name(name)
local response = ReadMsg() 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 if output_name ~= nil then
return new_output({ name = output_names[1] }) return new_output({ name = output_name })
else else
return nil return nil
end end
@ -78,7 +78,7 @@ end
---Get outputs by their model. ---Get outputs by their model.
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays. ---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
---@param model string The model of the output(s). ---@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) function output.get_by_model(model)
SendRequest({ SendRequest({
GetOutputsByModel = { GetOutputsByModel = {
@ -103,7 +103,7 @@ end
--- ---
---@param width integer The width of the outputs, in pixels. ---@param width integer The width of the outputs, in pixels.
---@param height integer The height 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) function output.get_by_res(width, height)
SendRequest({ SendRequest({
GetOutputsByRes = { GetOutputsByRes = {
@ -149,10 +149,10 @@ function output.get_focused()
local response = ReadMsg() 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 if output_name ~= nil then
return new_output({ name = output_names[1] }) return new_output({ name = output_name })
else else
return nil return nil
end end

View file

@ -86,6 +86,7 @@ function pinnacle.setup(config_func)
function SendMsg(data) function SendMsg(data)
local encoded = msgpack.encode(data) local encoded = msgpack.encode(data)
assert(encoded) assert(encoded)
-- print(encoded)
local len = encoded:len() local len = encoded:len()
socket.send(socket_fd, string.pack("=I4", len)) socket.send(socket_fd, string.pack("=I4", len))
socket.send(socket_fd, encoded) socket.send(socket_fd, encoded)

View file

@ -6,11 +6,6 @@
---@class Window ---@class Window
---@field private id integer The internal id of this 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 = {} local win = {}
---@param props Window ---@param props Window
@ -25,21 +20,32 @@ local function new_window(props)
end end
---Set a window's size. ---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? } ---@param size { w: integer?, h: integer? }
function win:set_size(size) function win:set_size(size)
self.size = {
w = size.w or self.size.w,
h = size.h or self.size.h,
}
SendMsg({ SendMsg({
SetWindowSize = { SetWindowSize = {
window_id = self.id, window_id = self.id,
size = { self.size.w, self.size.h }, width = size.w,
height = size.h,
}, },
}) })
end end
---Move a window to a tag, removing all other ones. ---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. ---@param name string The name of the tag.
function win:move_to_tag(name) function win:move_to_tag(name)
SendMsg({ SendMsg({
@ -51,6 +57,15 @@ function win:move_to_tag(name)
end end
---Toggle the specified tag for this window. ---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. ---@param name string The name of the tag.
function win:toggle_tag(name) function win:toggle_tag(name)
SendMsg({ SendMsg({
@ -62,6 +77,14 @@ function win:toggle_tag(name)
end end
---Close this window. ---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() function win:close()
SendMsg({ SendMsg({
CloseWindow = { CloseWindow = {
@ -71,6 +94,11 @@ function win:close()
end end
---Toggle this window's floating status. ---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() function win:toggle_floating()
SendMsg({ SendMsg({
ToggleFloating = { ToggleFloating = {
@ -80,9 +108,127 @@ function win:toggle_floating()
end end
---Get a window's size. ---Get a window's size.
---@return { w: integer, h: integer } ---
function win:get_size() ---### Example
return self.size ---```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 end
------------------------------------------------------------------- -------------------------------------------------------------------

View file

@ -33,7 +33,10 @@ pub enum Msg {
}, },
SetWindowSize { SetWindowSize {
window_id: WindowId, window_id: WindowId,
size: (i32, i32), #[serde(default)]
width: Option<i32>,
#[serde(default)]
height: Option<i32>,
}, },
MoveWindowToTag { MoveWindowToTag {
window_id: WindowId, window_id: WindowId,
@ -96,14 +99,22 @@ pub struct RequestId(pub u32);
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
/// Messages that require a server response, usually to provide some data. /// Messages that require a server response, usually to provide some data.
pub enum Request { pub enum Request {
// Windows
GetWindowByAppId { app_id: String }, GetWindowByAppId { app_id: String },
GetWindowByTitle { title: String }, GetWindowByTitle { title: String },
GetWindowByFocus, GetWindowByFocus,
GetAllWindows, 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 }, GetOutputByName { output_name: String },
GetOutputsByModel { model: String }, GetOutputsByModel { model: String },
GetOutputsByRes { res: (u32, u32) }, GetOutputsByRes { res: (u32, u32) },
GetOutputByFocus, GetOutputByFocus,
// Tags
GetTagsByOutput { output_name: String }, GetTagsByOutput { output_name: String },
GetTagActive { tag_id: TagId }, GetTagActive { tag_id: TagId },
GetTagName { tag_id: TagId }, GetTagName { tag_id: TagId },
@ -187,6 +198,12 @@ pub enum Args {
pub enum RequestResponse { pub enum RequestResponse {
Window { window_id: Option<WindowId> }, Window { window_id: Option<WindowId> },
Windows { window_ids: Vec<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> }, Outputs { output_names: Vec<String> },
Tags { tag_ids: Vec<TagId> }, Tags { tag_ids: Vec<TagId> },
TagActive { active: bool }, TagActive { active: bool },

View file

@ -159,7 +159,8 @@ impl Layout {
state.size.expect("size should have been set") state.size.expect("size should have been set")
}); });
let win1_loc = win1.with_state(|state| { 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 loc
}); });
@ -271,7 +272,8 @@ impl Layout {
state.size.expect("size should have been set") state.size.expect("size should have been set")
}); });
let win1_loc = win1.with_state(|state| { 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 loc
}); });

View file

@ -55,7 +55,7 @@ use smithay::{
dmabuf::DmabufFeedback, dmabuf::DmabufFeedback,
fractional_scale::FractionalScaleManagerState, fractional_scale::FractionalScaleManagerState,
output::OutputManagerState, output::OutputManagerState,
shell::xdg::XdgShellState, shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
shm::ShmState, shm::ShmState,
socket::ListeningSocketSource, socket::ListeningSocketSource,
viewporter::ViewporterState, viewporter::ViewporterState,
@ -119,20 +119,12 @@ impl<B: Backend> State<B> {
} }
Msg::SetMousebind { button: _ } => todo!(), Msg::SetMousebind { button: _ } => todo!(),
Msg::CloseWindow { window_id } => { Msg::CloseWindow { window_id } => {
if let Some(window) = self if let Some(window) = window_id.window(self) {
.windows
.iter()
.find(|win| win.with_state(|state| state.id == window_id))
{
window.toplevel().send_close(); window.toplevel().send_close();
} }
} }
Msg::ToggleFloating { window_id } => { Msg::ToggleFloating { window_id } => {
if let Some(window) = self if let Some(window) = window_id.window(self) {
.windows
.iter()
.find(|win| win.with_state(|state| state.id == window_id)).cloned()
{
crate::window::toggle_floating(self, &window); crate::window::toggle_floating(self, &window);
} }
} }
@ -144,23 +136,30 @@ impl<B: Backend> State<B> {
self.handle_spawn(command, callback_id); self.handle_spawn(command, callback_id);
} }
Msg::SetWindowSize { window_id, size } => { Msg::SetWindowSize {
let Some(window) = self.space.elements().find(|&win| { window_id,
win.with_state( |state| state.id == window_id) width,
}) else { return; }; height,
} => {
let Some(window) = window_id.window(self) else { return };
// TODO: tiled vs floating // TODO: tiled vs floating
let window_size = window.geometry().size;
window.toplevel().with_pending_state(|state| { 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(); window.toplevel().send_pending_configure();
} }
Msg::MoveWindowToTag { window_id, tag_id } => { Msg::MoveWindowToTag { window_id, tag_id } => {
if let Some(window) = self if let Some(window) = window_id.window(self) {
.windows
.iter()
.find(|&win| win.with_state(|state| state.id == window_id))
{
window.with_state(|state| { window.with_state(|state| {
self.focus_state self.focus_state
.focused_output .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 } => { Msg::ToggleTagOnWindow { window_id, tag_id } => {
if let Some(window) = self if let Some(window) = window_id.window(self) {
.windows
.iter()
.find(|&win| win.with_state(|state| state.id == window_id))
{
window.with_state(|state| { window.with_state(|state| {
self.focus_state self.focus_state
.focused_output .focused_output
@ -205,14 +199,21 @@ impl<B: Backend> State<B> {
self.re_layout(&output); self.re_layout(&output);
} }
} }
Msg::ToggleTag { output_name, tag_name } => { Msg::ToggleTag {
output_name,
tag_name,
} => {
tracing::debug!("ToggleTag"); 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 { if let Some(output) = output {
output.with_state(|state| { 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()); tracing::debug!("Setting tag {tag:?} to {}", !tag.active());
tag.set_active(!tag.active()); tag.set_active(!tag.active());
} }
@ -220,10 +221,16 @@ impl<B: Backend> State<B> {
self.re_layout(&output); self.re_layout(&output);
} }
} }
Msg::SwitchToTag { output_name, tag_name } => { Msg::SwitchToTag {
let output = self.space.outputs().find(|op| op.name() == output_name).cloned(); output_name,
tag_name,
} => {
let output = self
.space
.outputs()
.find(|op| op.name() == output_name)
.cloned();
if let Some(output) = output { if let Some(output) = output {
output.with_state(|state| { output.with_state(|state| {
if !state.tags.iter().any(|tag| tag.name() == tag_name) { if !state.tags.iter().any(|tag| tag.name() == tag_name) {
// TODO: notify error // TODO: notify error
@ -233,7 +240,11 @@ impl<B: Backend> State<B> {
tag.set_active(false); 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!() unreachable!()
}; };
tag.set_active(true); tag.set_active(true);
@ -252,21 +263,25 @@ impl<B: Backend> State<B> {
} }
} }
// TODO: add output // TODO: add output
Msg::AddTags { output_name, tag_names } => { Msg::AddTags {
output_name,
tag_names,
} => {
if let Some(output) = self if let Some(output) = self
.space .space
.outputs() .outputs()
.find(|output| output.name() == output_name) .find(|output| output.name() == output_name)
{ {
output.with_state(|state| { output.with_state(|state| {
state state.tags.extend(tag_names.iter().cloned().map(Tag::new));
.tags
.extend(tag_names.iter().cloned().map(Tag::new));
tracing::debug!("tags added, are now {:?}", state.tags); 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 if let Some(output) = self
.space .space
.outputs() .outputs()
@ -277,12 +292,20 @@ impl<B: Backend> State<B> {
}); });
} }
} }
Msg::SetLayout { output_name, tag_name, layout } => { Msg::SetLayout {
let output = self.space.outputs().find(|op| op.name() == output_name).cloned(); output_name,
tag_name,
layout,
} => {
let output = self
.space
.outputs()
.find(|op| op.name() == output_name)
.cloned();
if let Some(output) = output { if let Some(output) = output {
output.with_state(|state| { 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); tag.set_layout(layout);
} }
}); });
@ -317,194 +340,258 @@ impl<B: Backend> State<B> {
} }
Msg::Request(request) => { Msg::Request(request) => {
let stream = self self.handle_request(request);
.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<_>>();
// FIXME: figure out what to do if error fn handle_request(&mut self, request: Request) {
crate::api::send_to_client( let stream = self
&mut stream, .api_state
&OutgoingMsg::RequestResponse { .stream
response: RequestResponse::Windows { .as_ref()
window_ids, .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"); )
} .expect("Send to client failed");
Request::GetOutputByName { output_name } => { }
// TODO: name better None => {
let names = self crate::api::send_to_client(
.space &mut stream,
.outputs() &OutgoingMsg::RequestResponse {
.find(|output| output.name() == output_name) response: RequestResponse::Window { window_id: None },
.map(|output| output.name()); },
crate::api::send_to_client( )
&mut stream, .expect("Send to client failed");
&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();
}
}
} }
}, },
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) { pub fn re_layout(&mut self, output: &Output) {
let windows = self.windows.iter().filter(|win| { let windows = self
win.with_state(|state| state.tags.iter().any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output))) .windows
}).cloned().collect::<Vec<_>>(); .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 (render, do_not_render) = output.with_state(|state| {
let first_tag = state.focused_tags().next(); let first_tag = state.focused_tags().next();
if let Some(first_tag) = first_tag { 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 /// Schedule something to be done when windows have finished committing and have become
/// idle. /// idle.
pub fn schedule_on_commit<F, B: Backend>(data: &mut CalloopData<B>, windows: Vec<Window>, on_commit: F) pub fn schedule_on_commit<F, B: Backend>(
where data: &mut CalloopData<B>,
windows: Vec<Window>,
on_commit: F,
) where
F: FnOnce(&mut CalloopData<B>) + 'static, F: FnOnce(&mut CalloopData<B>) + 'static,
{ {
for window in windows.iter() { 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| { data.state.loop_handle.insert_idle(|data| {
schedule_on_commit(data, windows, on_commit); schedule_on_commit(data, windows, on_commit);
}); });

View file

@ -15,18 +15,30 @@ use smithay::{
utils::{Logical, Point, Serial, Size}, 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)] #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WindowId(u32); pub struct WindowId(u32);
// TODO: this probably doesn't need to be atomic
static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0); static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
impl WindowId { impl WindowId {
pub fn next() -> Self { pub fn next() -> Self {
Self(WINDOW_ID_COUNTER.fetch_add(1, Ordering::Relaxed)) 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 { pub struct WindowState {