Add to and simplify output in API

This commit is contained in:
Ottatop 2023-07-19 18:55:22 -05:00
parent f2b54be2fc
commit 53f29086b6
8 changed files with 391 additions and 160 deletions

View file

@ -66,14 +66,28 @@ require("pinnacle").setup(function(pinnacle)
-- Just testing stuff -- Just testing stuff
input.keybind({ mod_key }, keys.h, function() input.keybind({ mod_key }, keys.h, function()
local win = window.get_focused() -- local win = window.get_focused()
if win ~= nil then -- if win ~= nil then
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil")) -- 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("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
print("class: " .. (win:class() or "nil")) -- print("class: " .. (win:class() or "nil"))
print("title: " .. (win:title() or "nil")) -- print("title: " .. (win:title() or "nil"))
print("float: " .. tostring(win:floating())) -- print("float: " .. tostring(win:floating()))
end -- end
local op = output.get_focused() --[[@as Output]]
print("res: " .. (op:res() and (op:res().w .. ", " .. op:res().h) or "nil"))
print("loc: " .. (op:loc() and (op:loc().x .. ", " .. op:loc().y) or "nil"))
print("rr: " .. (op:refresh_rate() or "nil"))
print("make: " .. (op:make() or "nil"))
print("model: " .. (op:model() or "nil"))
print("focused: " .. (tostring(op:focused())))
-- local tags = tag.get_on_output(output.get_focused())
-- for _, tg in pairs(tags) do
-- print(tg:name())
-- print(tg:output() and tg:output().name or "nil output")
-- end
end) end)
-- Tags --------------------------------------------------------------------------- -- Tags ---------------------------------------------------------------------------
@ -103,6 +117,9 @@ require("pinnacle").setup(function(pinnacle)
for _, tg in pairs(tags) do for _, tg in pairs(tags) do
if tg:active() then if tg:active() then
local name = tg:name() local name = tg:name()
if name == nil then
return
end
tg:set_layout(layouts[indices[name] or 1]) tg:set_layout(layouts[indices[name] or 1])
if indices[name] == nil then if indices[name] == nil then
indices[name] = 2 indices[name] = 2
@ -122,6 +139,9 @@ require("pinnacle").setup(function(pinnacle)
for _, tg in pairs(tags) do for _, tg in pairs(tags) do
if tg:active() then if tg:active() then
local name = tg:name() local name = tg:name()
if name == nil then
return
end
tg:set_layout(layouts[indices[name] or #layouts]) tg:set_layout(layouts[indices[name] or #layouts])
if indices[name] == nil then if indices[name] == nil then
indices[name] = #layouts - 1 indices[name] = #layouts - 1

View file

@ -41,14 +41,15 @@
---@field GetWindowClass { window_id: WindowId } ---@field GetWindowClass { window_id: WindowId }
---@field GetWindowTitle { window_id: WindowId } ---@field GetWindowTitle { window_id: WindowId }
--Outputs --Outputs
---@field GetOutputByName { output_name: OutputName } ---@field GetOutputProps { output_name: string }
---@field GetOutputsByModel { model: string } --Tags
---@field GetOutputsByRes { res: integer[] }
---@field GetTagsByOutput { output_name: string } ---@field GetTagsByOutput { output_name: string }
---@field GetTagsByName { tag_name: string }
---@field GetTagOutput { tag_id: TagId }
---@field GetTagActive { tag_id: TagId } ---@field GetTagActive { tag_id: TagId }
---@field GetTagName { tag_id: TagId } ---@field GetTagName { tag_id: TagId }
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus" ---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputs"
---@class IncomingMsg ---@class IncomingMsg
---@field CallCallback { callback_id: integer, args: Args } ---@field CallCallback { callback_id: integer, args: Args }
@ -66,15 +67,16 @@
--Windows --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 WindowSize { size: integer[]? }
---@field WindowLocation { loc: (integer[])? } ---@field WindowLocation { loc: integer[]? }
---@field WindowClass { class: string? } ---@field WindowClass { class: string? }
---@field WindowTitle { title: string? } ---@field WindowTitle { title: string? }
---@field WindowFloating { floating: boolean? } ---@field WindowFloating { floating: boolean? }
--Outputs --Outputs
---@field Output { output_name: OutputName? } ---@field Output { output_name: OutputName? }
---@field Outputs { output_names: OutputName[] } ---@field Outputs { output_names: OutputName[] }
---@field OutputProps { make: string?, model: string?, loc: integer[]?, res: integer[]?, refresh_rate: integer?, physical_size: integer[]?, focused: boolean? }
--Tags --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

@ -26,10 +26,121 @@ function op:add_tags_table(names)
require("tag").add_table(self, names) require("tag").add_table(self, names)
end end
---Add methods to this output. ---Get this output's make.
---@return string|nil
function op:make()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
return props.make
end
---Get this output's model.
---@return string|nil
function op:model()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
return props.model
end
---Get this output's location in the global space.
---@return { x: integer, y: integer }|nil
function op:loc()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
if props.loc == nil then
return nil
else
return { x = props.loc[1], y = props.loc[2] }
end
end
---Get this output's resolution in pixels.
---@return { w: integer, h: integer }|nil
function op:res()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
if props.res == nil then
return nil
else
return { w = props.res[1], h = props.res[2] }
end
end
---Get this output's refresh rate in millihertz.
---For example, 60Hz will be returned as 60000.
---@return integer|nil
function op:refresh_rate()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
return props.refresh_rate
end
---Get this output's physical size in millimeters.
---@return { w: integer, h: integer }|nil
function op:physical_size()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
if props.physical_size == nil then
return nil
else
return { w = props.physical_size[1], h = props.physical_size[2] }
end
end
---Get whether or not this output is focused. This is currently defined as having the cursor on it.
---@return boolean|nil
function op:focused()
SendRequest({
GetOutputProps = {
output_name = self.name,
},
})
local response = ReadMsg()
local props = response.RequestResponse.response.OutputProps
return props.focused
end
---This is an internal global function used to create an output object from an output name.
---@param props Output ---@param props Output
---@return Output ---@return Output
local function new_output(props) function NewOutput(props)
-- Copy functions over -- Copy functions over
for k, v in pairs(op) do for k, v in pairs(op) do
props[k] = v props[k] = v
@ -40,6 +151,7 @@ end
------------------------------------------------------ ------------------------------------------------------
---@class OutputGlobal
local output = {} local output = {}
---Get an output by its name. ---Get an output by its name.
@ -56,21 +168,17 @@ local output = {}
---@param name string The name of the output. ---@param name string The name of the output.
---@return Output|nil output The output, or nil if none have the provided name. ---@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("GetOutputs")
GetOutputByName = {
output_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 for _, output_name in pairs(output_names) do
if output_name == name then
if output_name ~= nil then return NewOutput({ name = output_name })
return new_output({ name = output_name }) end
else
return nil
end end
return nil
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.
@ -80,20 +188,17 @@ end
---@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. ---@return Output[] outputs All outputs with this model.
function output.get_by_model(model) function output.get_by_model(model)
SendRequest({ SendRequest("GetOutputs")
GetOutputsByModel = {
model = model,
},
})
local response = ReadMsg() local response = ReadMsg()
local output_names = response.RequestResponse.response.Outputs.output_names local output_names = response.RequestResponse.response.Outputs.output_names
---@type Output ---@type Output[]
local outputs = {} local outputs = {}
for _, v in pairs(output_names) do for _, output_name in pairs(output_names) do
table.insert(outputs, new_output({ name = v })) local o = NewOutput({ name = output_name })
if o:model() == model then
table.insert(outputs, o)
end
end end
return outputs return outputs
@ -105,11 +210,7 @@ end
---@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. ---@return Output[] outputs All outputs with this resolution.
function output.get_by_res(width, height) function output.get_by_res(width, height)
SendRequest({ SendRequest("GetOutputs")
GetOutputsByRes = {
res = { width, height },
},
})
local response = ReadMsg() local response = ReadMsg()
@ -118,7 +219,10 @@ function output.get_by_res(width, height)
---@type Output ---@type Output
local outputs = {} local outputs = {}
for _, output_name in pairs(output_names) do for _, output_name in pairs(output_names) do
table.insert(outputs, new_output({ name = output_name })) local o = NewOutput({ name = output_name })
if o:res() and o:res().w == width and o:res().h == height then
table.insert(outputs, o)
end
end end
return outputs return outputs
@ -145,17 +249,18 @@ end
---``` ---```
---@return Output|nil output The output, or nil if none are focused. ---@return Output|nil output The output, or nil if none are focused.
function output.get_focused() function output.get_focused()
SendRequest("GetOutputByFocus") SendRequest("GetOutputs")
local response = ReadMsg() local response = ReadMsg()
local output_names = response.RequestResponse.response.Outputs.output_names
local output_name = response.RequestResponse.response.Output.output_name for _, output_name in pairs(output_names) do
local o = NewOutput({ name = output_name })
if output_name ~= nil then if o:focused() then
return new_output({ name = output_name }) return o
else end
return nil
end end
return nil
end end
---Connect a function to be run on all current and future outputs. ---Connect a function to be run on all current and future outputs.
@ -170,7 +275,7 @@ function output.connect_for_all(func)
---@param args Args ---@param args Args
table.insert(CallbackTable, function(args) table.insert(CallbackTable, function(args)
local args = args.ConnectForAllOutputs local args = args.ConnectForAllOutputs
func(new_output({ name = args.output_name })) func(NewOutput({ name = args.output_name }))
end) end)
SendMsg({ SendMsg({
ConnectForAllOutputs = { ConnectForAllOutputs = {

View file

@ -82,6 +82,7 @@ function pinnacle.setup(config_func)
---@type fun(args: table?)[] ---@type fun(args: table?)[]
CallbackTable = {} CallbackTable = {}
---This is an internal global function used to send serialized messages to the Pinnacle server.
---@param data Msg ---@param data Msg
function SendMsg(data) function SendMsg(data)
local encoded = msgpack.encode(data) local encoded = msgpack.encode(data)
@ -92,6 +93,7 @@ function pinnacle.setup(config_func)
socket.send(socket_fd, encoded) socket.send(socket_fd, encoded)
end end
---This is an internal global function used to send requests to the Pinnacle server for information.
---@param data Request ---@param data Request
function SendRequest(data) function SendRequest(data)
SendMsg({ SendMsg({
@ -99,6 +101,8 @@ function pinnacle.setup(config_func)
}) })
end end
---This is an internal global function used to read messages sent from the server.
---These are used to call user-defined functions and provide requested information.
function ReadMsg() function ReadMsg()
local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4) local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4)
assert(msg_len_bytes) assert(msg_len_bytes)

View file

@ -31,7 +31,7 @@ local function new_tag(props)
end end
---Get this tag's active status. ---Get this tag's active status.
---@return boolean active True if the tag is active, otherwise false. ---@return boolean|nil active `true` if the tag is active, `false` if not, and `nil` if the tag doesn't exist.
function tg:active() function tg:active()
SendRequest({ SendRequest({
GetTagActive = { GetTagActive = {
@ -44,6 +44,8 @@ function tg:active()
return active return active
end end
---Get this tag's name.
---@return string|nil name The name of this tag, or nil if it doesn't exist.
function tg:name() function tg:name()
SendRequest({ SendRequest({
GetTagName = { GetTagName = {
@ -56,10 +58,32 @@ function tg:name()
return name return name
end end
---Get this tag's output.
---@return Output|nil output The output this tag is on, or nil if the tag doesn't exist.
function tg:output()
SendRequest({
GetTagOutput = {
tag_id = self.id,
},
})
local response = ReadMsg()
local output_name = response.RequestResponse.response.Output.output_name
if output_name == nil then
return nil
else
return NewOutput({ name = output_name })
end
end
---Set this tag's layout. ---Set this tag's layout.
---@param layout Layout ---@param layout Layout
function tg:set_layout(layout) -- TODO: output param function tg:set_layout(layout) -- TODO: output param
tag.set_layout(self:name(), layout) local name = self:name()
if name ~= nil then
tag.set_layout(name, layout)
end
end end
----------------------------------------------------------- -----------------------------------------------------------
@ -68,12 +92,14 @@ end
--- ---
---If you need to add the names as a table, use `tag.add_table` instead. ---If you need to add the names as a table, use `tag.add_table` instead.
--- ---
---You can also do `op:add_tags(...)`.
---
---### Example ---### Example
--- ---
---```lua ---```lua
---local output = output.get_by_name("DP-1") ---local op = output.get_by_name("DP-1")
---if output ~= nil then ---if op ~= nil then
--- tag.add(output, "1", "2", "3", "4", "5") -- Add tags with names 1-5 --- tag.add(op, "1", "2", "3", "4", "5") -- Add tags with names 1-5
---end ---end
---``` ---```
---@param output Output The output you want these tags to be added to. ---@param output Output The output you want these tags to be added to.
@ -237,4 +263,28 @@ function tag.get_on_output(output)
return tags return tags
end end
---Get all tags with this name across all outputs.
---@param name string The name of the tags you want.
---@return Tag[]
function tag.get_by_name(name)
SendRequest({
GetTagsByName = {
tag_name = name,
},
})
local response = ReadMsg()
local tag_ids = response.RequestResponse.response.Tags.tag_ids
---@type Tag[]
local tags = {}
for _, tag_id in pairs(tag_ids) do
table.insert(tags, new_tag({ id = tag_id }))
end
return tags
end
return tag return tag

View file

@ -110,12 +110,12 @@ pub enum Request {
GetWindowClass { window_id: WindowId }, GetWindowClass { window_id: WindowId },
GetWindowTitle { window_id: WindowId }, GetWindowTitle { window_id: WindowId },
// Outputs // Outputs
GetOutputByName { output_name: String }, GetOutputs,
GetOutputsByModel { model: String }, GetOutputProps { output_name: String },
GetOutputsByRes { res: (u32, u32) },
GetOutputByFocus,
// Tags // Tags
GetTagsByOutput { output_name: String }, GetTagsByOutput { output_name: String },
GetTagsByName { tag_name: String },
GetTagOutput { tag_id: TagId },
GetTagActive { tag_id: TagId }, GetTagActive { tag_id: TagId },
GetTagName { tag_id: TagId }, GetTagName { tag_id: TagId },
} }
@ -196,16 +196,56 @@ pub enum Args {
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestResponse { pub enum RequestResponse {
Window { window_id: Option<WindowId> }, Window {
Windows { window_ids: Vec<WindowId> }, window_id: Option<WindowId>,
WindowSize { size: Option<(i32, i32)> }, },
WindowLocation { loc: Option<(i32, i32)> }, Windows {
WindowClass { class: Option<String> }, window_ids: Vec<WindowId>,
WindowTitle { title: Option<String> }, },
WindowFloating { floating: Option<bool> }, WindowSize {
Output { output_name: Option<String> }, size: Option<(i32, i32)>,
Outputs { output_names: Vec<String> }, },
Tags { tag_ids: Vec<TagId> }, WindowLocation {
TagActive { active: bool }, loc: Option<(i32, i32)>,
TagName { name: String }, },
WindowClass {
class: Option<String>,
},
WindowTitle {
title: Option<String>,
},
WindowFloating {
floating: Option<bool>,
},
Output {
output_name: Option<String>,
},
Outputs {
output_names: Vec<String>,
},
OutputProps {
/// The make of the output.
make: Option<String>,
/// The model of the output.
model: Option<String>,
/// The location of the output in the space.
loc: Option<(i32, i32)>,
/// The resolution of the output.
res: Option<(i32, i32)>,
/// The refresh rate of the output.
refresh_rate: Option<i32>,
/// The size of the output, in millimeters.
physical_size: Option<(i32, i32)>,
/// Whether the output is focused or not.
focused: Option<bool>,
},
Tags {
tag_ids: Vec<TagId>,
},
TagActive {
active: Option<bool>,
},
TagName {
name: Option<String>,
},
} }

View file

@ -471,74 +471,63 @@ impl<B: Backend> State<B> {
) )
.expect("failed to send to client"); .expect("failed to send to client");
} }
Request::GetOutputByName { output_name } => { Request::GetOutputs => {
// TODO: name better let output_names = self
let name = self
.space .space
.outputs() .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()) .map(|output| output.name())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
crate::api::send_to_client( crate::api::send_to_client(
&mut stream, &mut stream,
&OutgoingMsg::RequestResponse { &OutgoingMsg::RequestResponse {
response: RequestResponse::Outputs { response: RequestResponse::Outputs { output_names },
output_names: names,
},
}, },
) )
.expect("failed to send to client"); .expect("failed to send to client");
} }
Request::GetOutputsByRes { res } => { Request::GetOutputProps { output_name } => {
let names = self let output = self
.space .space
.outputs() .outputs()
.filter_map(|output| { .find(|output| output.name() == output_name);
if let Some(mode) = output.current_mode() { let res = output.as_ref().and_then(|output| {
if mode.size == (res.0 as i32, res.1 as i32).into() { output.current_mode().map(|mode| (mode.size.w, mode.size.h))
Some(output.name()) });
} else { let refresh_rate = output
None .as_ref()
} .and_then(|output| output.current_mode().map(|mode| mode.refresh));
} else { let model = output
None .as_ref()
} .map(|output| output.physical_properties().model);
}) let physical_size = output.as_ref().map(|output| {
.collect::<Vec<_>>(); (
crate::api::send_to_client( output.physical_properties().size.w,
&mut stream, output.physical_properties().size.h,
&OutgoingMsg::RequestResponse { )
response: RequestResponse::Outputs { });
output_names: names, let make = output
}, .as_ref()
}, .map(|output| output.physical_properties().make);
) let loc = output
.expect("failed to send to client"); .as_ref()
} .map(|output| (output.current_location().x, output.current_location().y));
Request::GetOutputByFocus => { let focused = self
let name = self
.focus_state .focus_state
.focused_output .focused_output
.as_ref() .as_ref()
.map(|output| output.name()); .and_then(|foc_op| output.map(|op| op == foc_op));
crate::api::send_to_client( crate::api::send_to_client(
&mut stream, &mut stream,
&OutgoingMsg::RequestResponse { &OutgoingMsg::RequestResponse {
response: RequestResponse::Output { output_name: name }, response: RequestResponse::OutputProps {
make,
model,
loc,
res,
refresh_rate,
physical_size,
focused,
},
}, },
) )
.expect("failed to send to client"); .expect("failed to send to client");
@ -558,39 +547,54 @@ impl<B: Backend> State<B> {
.expect("failed to send to client"); .expect("failed to send to client");
} }
} }
Request::GetTagActive { tag_id } => { Request::GetTagsByName { tag_name } => {
let tag = self let tag_ids = self
.space .space
.outputs() .outputs()
.flat_map(|op| op.with_state(|state| state.tags.clone())) .flat_map(|op| op.with_state(|state| state.tags.clone()))
.find(|tag| tag.id() == tag_id); .filter(|tag| tag.name() == tag_name)
if let Some(tag) = tag { .map(|tag| tag.id())
crate::api::send_to_client( .collect::<Vec<_>>();
&mut stream, crate::api::send_to_client(
&OutgoingMsg::RequestResponse { &mut stream,
response: RequestResponse::TagActive { &OutgoingMsg::RequestResponse {
active: tag.active(), response: RequestResponse::Tags { tag_ids },
}, },
}, )
) .expect("failed to send to client");
.expect("failed to send to client"); }
} Request::GetTagOutput { tag_id } => {
let output_name = tag_id
.tag(self)
.and_then(|tag| tag.output(self))
.map(|output| output.name());
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::Output { output_name },
},
)
.expect("failed to send to client");
}
Request::GetTagActive { tag_id } => {
let active = tag_id.tag(self).map(|tag| tag.active());
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::TagActive { active },
},
)
.expect("failed to send to client");
} }
Request::GetTagName { tag_id } => { Request::GetTagName { tag_id } => {
let tag = self let name = tag_id.tag(self).map(|tag| tag.name());
.space crate::api::send_to_client(
.outputs() &mut stream,
.flat_map(|op| op.with_state(|state| state.tags.clone())) &OutgoingMsg::RequestResponse {
.find(|tag| tag.id() == tag_id); response: RequestResponse::TagName { name },
if let Some(tag) = tag { },
crate::api::send_to_client( )
&mut stream, .expect("failed to send to client");
&OutgoingMsg::RequestResponse {
response: RequestResponse::TagName { name: tag.name() },
},
)
.expect("failed to send to client");
}
} }
} }
} }
@ -752,7 +756,7 @@ impl<B: Backend> State<B> {
state state
.tags .tags
.iter() .iter()
.any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output)) .any(|tag| tag.output(self).is_some_and(|op| &op == output))
}) })
}) })
.cloned() .cloned()

View file

@ -28,6 +28,14 @@ impl TagId {
fn next() -> Self { fn next() -> Self {
Self(TAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed)) Self(TAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
} }
pub fn tag<B: Backend>(&self, state: &State<B>) -> Option<Tag> {
state
.space
.outputs()
.flat_map(|op| op.with_state(|state| state.tags.clone()))
.find(|tag| &tag.id() == self)
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -88,13 +96,11 @@ impl Tag {
layout: Layout::MasterStack, // TODO: get from config layout: Layout::MasterStack, // TODO: get from config
}))) })))
} }
} pub fn output<B: Backend>(&self, state: &State<B>) -> Option<Output> {
state
impl<B: Backend> State<B> { .space
pub fn output_for_tag(&self, tag: &Tag) -> Option<Output> {
self.space
.outputs() .outputs()
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == tag))) .find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == self)))
.cloned() .cloned()
} }
} }