From b1ee8e03c168d75cf56429a1d9ea1d1b4c0a1aca Mon Sep 17 00:00:00 2001 From: Ottatop Date: Tue, 18 Jul 2023 10:31:08 -0500 Subject: [PATCH] Add more stuff to API --- api/lua/example_config.lua | 18 ++- api/lua/msg.lua | 5 + api/lua/output.lua | 38 ++++- api/lua/tag.lua | 55 ++++++- src/api/msg.rs | 3 + src/state.rs | 317 ++++++++++++++++++------------------- src/tag.rs | 5 + 7 files changed, 269 insertions(+), 172 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 2811e46..cf858cf 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -51,17 +51,11 @@ require("pinnacle").setup(function(pinnacle) process.spawn("nautilus") end) - input.keybind({ mod_key }, keys.g, function() - local op = output.get_by_res(2560, 1440) - for _, v in pairs(op) do - print(v.name) - end - end) - -- Tags --------------------------------------------------------------------------- output.connect_for_all(function(op) - tag.add(op, "1", "2", "3", "4", "5") + op:add_tags("1", "2", "3", "4", "5") + -- Same as tag.add(op, "1", "2", "3", "4", "5") tag.toggle("1", op) end) @@ -85,6 +79,14 @@ require("pinnacle").setup(function(pinnacle) index = index + 1 end end) + input.keybind({ mod_key, "Shift" }, keys.space, function() + tag.set_layout("1", layouts[index]) + if index - 1 < 1 then + index = #layouts + else + index = index - 1 + end + end) input.keybind({ mod_key }, keys.KEY_1, function() tag.switch_to("1") diff --git a/api/lua/msg.lua b/api/lua/msg.lua index b889a3d..1eb04fe 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -39,6 +39,7 @@ ---@field GetOutputByName { name: string } ---@field GetOutputsByModel { model: string } ---@field GetOutputsByRes { res: integer[] } +---@field GetTagsByOutput { output: string } ---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus" @@ -54,6 +55,7 @@ ---@field Window { window: WindowProperties } ---@field GetAllWindows { windows: WindowProperties[] } ---@field Outputs { names: string[] } +---@field Tags { tags: TagProperties[] } ---@class WindowProperties ---@field id integer @@ -62,3 +64,6 @@ ---@field size integer[] A two element int array, \[1\] = w, \[2\] = h ---@field location integer[] A two element int array, \[1\] = x, \[2\] = y ---@field floating boolean + +---@class TagProperties +---@field id integer diff --git a/api/lua/output.lua b/api/lua/output.lua index 1e4fbe5..b5dac89 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -8,6 +8,24 @@ ---@field name string The name of this output (or rather, of its connector). local op = {} +---Get all tags on this output. See `tag.get_on_output`. +---@return Tag[] +function op:tags() + return require("tag").get_on_output(self) +end + +---Add tags to this output. See `tag.add`. +---@param ... string The names of the tags you want to add. +function op:add_tags(...) + require("tag").add(self, ...) +end + +---Add tags to this output as a table. See `tag.add_table`. +---@param names string[] The names of the tags you want to add, as a table. +function op:add_tags_table(names) + require("tag").add_table(self, names) +end + ---Add methods to this output. ---@param props Output ---@return Output @@ -57,7 +75,7 @@ function output.get_by_name(name) end end ----NOTE: This may or may not be what is reported by other monitor listing utilities. Pinnacle currently fails to pick up one of my monitors' models when it is correctly picked up by tools like wlr-randr. I'll fix this in the future. +---Note: This may or may not be what is reported by other monitor listing utilities. Pinnacle currently fails to pick up one of my monitors' models when it is correctly picked up by tools like wlr-randr. I'll fix this in the future. --- ---Get outputs by their model. ---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays. @@ -113,6 +131,24 @@ function output.get_by_res(width, height) end ---Get the currently focused output. This is currently implemented as the one with the cursor on it. +--- +---This function may return nil, which means you may get a warning if you try to use it without checking for nil. +---Usually this function will not be nil unless you unplug all monitors, so instead of checking, +---you can ignore the warning by either forcing the type to be non-nil with an inline comment: +---```lua +---local op = output.get_focused() --[[@as Output]] +---``` +---or by disabling nil check warnings for the line: +---```lua +---local op = output.get_focused() +------@diagnostic disable-next-line:need-check-nil +---local tags_on_output = op:tags() +---``` +---Type checking done by Lua LS isn't perfect. +---Note that directly using the result of this function inline will *not* raise a warning, so be careful. +---```lua +---local tags = output.get_focused():tags() -- will NOT warn for nil +---``` ---@return Output|nil output The output, or nil if none are focused. function output.get_focused() SendMsg({ diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 326990c..3880506 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -13,6 +13,21 @@ ---| "CornerBottomLeft" # One main corner window in the bottom left with a column of windows on the right and a row on the top. ---| "CornerBottomRight" # One main corner window in the bottom right with a column of windows on the left and a row on the top. +---@class Tag +---@field private id integer The internal id of this tag. +local tg = {} + +---@param props Tag +---@return Tag +local function new_tag(props) + -- Copy functions over + for k, v in pairs(tg) do + props[k] = v + end + + return props +end + local tag = {} ---Add tags. @@ -53,12 +68,12 @@ end ---end ---``` ---@param output Output The output you want these tags to be added to. ----@param tags string[] The names of the new tags you want to add, as a table. -function tag.add_table(output, tags) +---@param names string[] The names of the new tags you want to add, as a table. +function tag.add_table(output, names) SendMsg({ AddTags = { output_name = output.name, - tags = tags, + tags = names, }, }) end @@ -156,4 +171,38 @@ function tag.set_layout(name, layout, output) end end end + +---Get all tags on the specified output. +--- +---You can also use `output_obj:tags()`, which delegates to this function: +---```lua +---local tags_on_output = output.get_focused():tags() +----- This is the same as +----- local tags_on_output = tag.get_on_output(output.get_focused()) +---``` +---@param output Output +---@return Tag[] +function tag.get_on_output(output) + SendMsg({ + Request = { + GetTagsByOutput = { + output = output.name, + }, + }, + }) + + local response = ReadMsg() + + local tag_props = response.RequestResponse.response.Tags.tags + + ---@type Tag[] + local tags = {} + + for _, prop in pairs(tag_props) do + table.insert(tags, new_tag({ id = prop.id })) + end + + return tags +end + return tag diff --git a/src/api/msg.rs b/src/api/msg.rs index e4930c8..482575d 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -9,6 +9,7 @@ use crate::{ layout::Layout, + tag::TagProperties, window::{window_state::WindowId, WindowProperties}, }; @@ -109,6 +110,7 @@ pub enum Request { GetOutputsByModel { model: String }, GetOutputsByRes { res: (u32, u32) }, GetOutputByFocus, + GetTagsByOutput { output: String }, } #[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] @@ -189,4 +191,5 @@ pub enum RequestResponse { Window { window: WindowProperties }, GetAllWindows { windows: Vec }, Outputs { names: Vec }, + Tags { tags: Vec }, } diff --git a/src/state.rs b/src/state.rs index 25d4b8e..056d3c0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -21,7 +21,7 @@ use crate::{ }, focus::FocusState, grab::resize_grab::ResizeSurfaceState, - tag::Tag, + tag::{Tag, TagProperties}, window::{window_state::WindowResizeState, WindowProperties}, }; use calloop::futures::Scheduler; @@ -310,12 +310,19 @@ impl State { self.loop_signal.stop(); } - Msg::Request(request) => match request { - Request::GetWindowByAppId { app_id } => todo!(), - Request::GetWindowByTitle { title } => todo!(), - Request::GetWindowByFocus => { - let Some(current_focus) = self.focus_state.current_focus() else { return; }; - let (app_id, title) = + 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 => { + let Some(current_focus) = self.focus_state.current_focus() else { return; }; + let (app_id, title) = compositor::with_states(current_focus.toplevel().wl_surface(), |states| { let lock = states .data_map @@ -325,38 +332,32 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = + let (window_id, floating) = current_focus.with_state(|state| (state.id, state.floating.is_floating())); - // TODO: unwrap - let location = self.space.element_location(¤t_focus).unwrap(); - let props = WindowProperties { - id: window_id, - app_id, - title, - size: current_focus.geometry().size.into(), - location: location.into(), - floating, - }; - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::Window { window: props }, - }, - ) - .expect("Send to client failed"); - } - Request::GetAllWindows => { - let window_props = self - .space - .elements() - .map(|win| { - let (app_id, title) = + // TODO: unwrap + let location = self.space.element_location(¤t_focus).unwrap(); + let props = WindowProperties { + id: window_id, + app_id, + title, + size: current_focus.geometry().size.into(), + location: location.into(), + floating, + }; + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Window { window: props }, + }, + ) + .expect("Send to client failed"); + } + Request::GetAllWindows => { + let window_props = self + .space + .elements() + .map(|win| { + let (app_id, title) = compositor::with_states(win.toplevel().wl_surface(), |states| { let lock = states .data_map @@ -366,134 +367,130 @@ impl State { .expect("Couldn't lock XdgToplevelSurfaceData"); (lock.app_id.clone(), lock.title.clone()) }); - let (window_id, floating) = + let (window_id, floating) = win.with_state(|state| (state.id, state.floating.is_floating())); - // TODO: unwrap - let location = self - .space - .element_location(win) - .expect("Window location doesn't exist"); - WindowProperties { - id: window_id, - app_id, - title, - size: win.geometry().size.into(), - location: location.into(), - floating, - } - }) - .collect::>(); + // TODO: unwrap + let location = self + .space + .element_location(win) + .expect("Window location doesn't exist"); + WindowProperties { + id: window_id, + app_id, + title, + size: win.geometry().size.into(), + location: location.into(), + floating, + } + }) + .collect::>(); - // FIXME: figure out what to do if error - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::GetAllWindows { - windows: window_props, + // FIXME: figure out what to do if error + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::GetAllWindows { + windows: window_props, + }, }, - }, - ) - .expect("Couldn't send to client"); - } - Request::GetOutputByName { name } => { - let names = self - .space - .outputs() - .filter(|output| output.name() == name) - .map(|output| output.name()) - .collect::>(); - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::Outputs { names }, - }, - ) - .unwrap(); - } - Request::GetOutputsByModel { model } => { - let names = self - .space - .outputs() - .filter(|output| output.physical_properties().model == model) - .map(|output| output.name()) - .collect::>(); - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::Outputs { 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()) + ) + .expect("Couldn't send to client"); + } + Request::GetOutputByName { name } => { + // TODO: name better + let names = self + .space + .outputs() + .find(|output| output.name() == name) + .map(|output| output.name()); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { + 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 { 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 } - } else { - None - } - }) - .collect::>(); - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::Outputs { names }, - }, - ) - .unwrap(); - } - Request::GetOutputByFocus => { - let names = self - .focus_state - .focused_output - .as_ref() - .map(|output| output.name()) - .into_iter() - .collect::>(); - let stream = self - .api_state - .stream - .as_ref() - .expect("Stream doesn't exist"); - let mut stream = stream.lock().expect("Couldn't lock stream"); - crate::api::send_to_client( - &mut stream, - &OutgoingMsg::RequestResponse { - response: RequestResponse::Outputs { names }, - }, - ) - .unwrap(); + }) + .collect::>(); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Outputs { 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 { names }, + }, + ) + .unwrap(); + } + Request::GetTagsByOutput { output } => { + let output = self + .space + .outputs() + .find(|op| op.name() == output); + if let Some(output) = output { + let tag_props = output.with_state(|state| { + state.tags + .iter() + .map(|tag| TagProperties { id: tag.id() }) + .collect::>() + }); + crate::api::send_to_client( + &mut stream, + &OutgoingMsg::RequestResponse { + response: RequestResponse::Tags { tags: tag_props } + }).unwrap(); + } + } } }, } diff --git a/src/tag.rs b/src/tag.rs index 9f866db..ed95841 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -90,6 +90,11 @@ impl Tag { } } +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TagProperties { + pub id: TagId, +} + impl State { pub fn output_for_tag(&self, tag: &Tag) -> Option { self.space