diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 58eb17b..1be76a1 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -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) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index 89c92d4..88741f8 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -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 } diff --git a/api/lua/output.lua b/api/lua/output.lua index 4a748b2..4223fa3 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -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 diff --git a/api/lua/pinnacle.lua b/api/lua/pinnacle.lua index 55aaf91..eb2de17 100644 --- a/api/lua/pinnacle.lua +++ b/api/lua/pinnacle.lua @@ -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) diff --git a/api/lua/window.lua b/api/lua/window.lua index d8dbf72..bec9b3c 100644 --- a/api/lua/window.lua +++ b/api/lua/window.lua @@ -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 ------------------------------------------------------------------- diff --git a/src/api/msg.rs b/src/api/msg.rs index 5ba22ca..d83f2c2 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -33,7 +33,10 @@ pub enum Msg { }, SetWindowSize { window_id: WindowId, - size: (i32, i32), + #[serde(default)] + width: Option, + #[serde(default)] + height: Option, }, 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 }, Windows { window_ids: Vec }, + WindowSize { size: Option<(i32, i32)> }, + WindowLocation { loc: Option<(i32, i32)> }, + WindowClass { class: Option }, + WindowTitle { title: Option }, + WindowFloating { floating: Option }, + Output { output_name: Option }, Outputs { output_names: Vec }, Tags { tag_ids: Vec }, TagActive { active: bool }, diff --git a/src/layout.rs b/src/layout.rs index 72768f5..159df6f 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -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 }); diff --git a/src/state.rs b/src/state.rs index 6fd4e63..ffd36ba 100644 --- a/src/state.rs +++ b/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 State { } 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 State { 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 State { } }); }); + 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 State { 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 State { 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 State { 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 State { } } // 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 State { }); } } - 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 State { } 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::>(); + 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::>(); - 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::>(); - 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::>(); - 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::>() - }); - 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::>(); + + // 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::() + .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::() + .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::>(); + 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::>(); + 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::>() + }); + 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 State { } 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::>(); + 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::>(); 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 State { } /// Schedule something to be done when windows have finished committing and have become /// idle. -pub fn schedule_on_commit(data: &mut CalloopData, windows: Vec, on_commit: F) - where +pub fn schedule_on_commit( + data: &mut CalloopData, + windows: Vec, + on_commit: F, +) where F: FnOnce(&mut CalloopData) + '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); }); diff --git a/src/window/window_state.rs b/src/window/window_state.rs index 50c50b5..7b4a698 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -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(&self, state: &State) -> Option { + state + .windows + .iter() + .find(|win| win.with_state(|state| &state.id == self)) + .cloned() + } } pub struct WindowState {