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
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
-- 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
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)
-- Tags ---------------------------------------------------------------------------
@ -103,6 +117,9 @@ require("pinnacle").setup(function(pinnacle)
for _, tg in pairs(tags) do
if tg:active() then
local name = tg:name()
if name == nil then
return
end
tg:set_layout(layouts[indices[name] or 1])
if indices[name] == nil then
indices[name] = 2
@ -122,6 +139,9 @@ require("pinnacle").setup(function(pinnacle)
for _, tg in pairs(tags) do
if tg:active() then
local name = tg:name()
if name == nil then
return
end
tg:set_layout(layouts[indices[name] or #layouts])
if indices[name] == nil then
indices[name] = #layouts - 1

View file

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

View file

@ -26,10 +26,121 @@ function op:add_tags_table(names)
require("tag").add_table(self, names)
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
---@return Output
local function new_output(props)
function NewOutput(props)
-- Copy functions over
for k, v in pairs(op) do
props[k] = v
@ -40,6 +151,7 @@ end
------------------------------------------------------
---@class OutputGlobal
local output = {}
---Get an output by its name.
@ -56,21 +168,17 @@ local output = {}
---@param name string The name of the output.
---@return Output|nil output The output, or nil if none have the provided name.
function output.get_by_name(name)
SendRequest({
GetOutputByName = {
output_name = name,
},
})
SendRequest("GetOutputs")
local response = ReadMsg()
local output_names = response.RequestResponse.response.Outputs.output_names
local output_name = response.RequestResponse.response.Output.output_name
if output_name ~= nil then
return new_output({ name = output_name })
else
return nil
for _, output_name in pairs(output_names) do
if output_name == name then
return NewOutput({ name = output_name })
end
end
return nil
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.
@ -80,20 +188,17 @@ end
---@param model string The model of the output(s).
---@return Output[] outputs All outputs with this model.
function output.get_by_model(model)
SendRequest({
GetOutputsByModel = {
model = model,
},
})
SendRequest("GetOutputs")
local response = ReadMsg()
local output_names = response.RequestResponse.response.Outputs.output_names
---@type Output
---@type Output[]
local outputs = {}
for _, v in pairs(output_names) do
table.insert(outputs, new_output({ name = v }))
for _, output_name in pairs(output_names) do
local o = NewOutput({ name = output_name })
if o:model() == model then
table.insert(outputs, o)
end
end
return outputs
@ -105,11 +210,7 @@ end
---@param height integer The height of the outputs, in pixels.
---@return Output[] outputs All outputs with this resolution.
function output.get_by_res(width, height)
SendRequest({
GetOutputsByRes = {
res = { width, height },
},
})
SendRequest("GetOutputs")
local response = ReadMsg()
@ -118,7 +219,10 @@ function output.get_by_res(width, height)
---@type Output
local outputs = {}
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
return outputs
@ -145,17 +249,18 @@ end
---```
---@return Output|nil output The output, or nil if none are focused.
function output.get_focused()
SendRequest("GetOutputByFocus")
SendRequest("GetOutputs")
local response = ReadMsg()
local output_names = response.RequestResponse.response.Outputs.output_names
local output_name = response.RequestResponse.response.Output.output_name
if output_name ~= nil then
return new_output({ name = output_name })
else
return nil
for _, output_name in pairs(output_names) do
local o = NewOutput({ name = output_name })
if o:focused() then
return o
end
end
return nil
end
---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
table.insert(CallbackTable, function(args)
local args = args.ConnectForAllOutputs
func(new_output({ name = args.output_name }))
func(NewOutput({ name = args.output_name }))
end)
SendMsg({
ConnectForAllOutputs = {

View file

@ -82,6 +82,7 @@ function pinnacle.setup(config_func)
---@type fun(args: table?)[]
CallbackTable = {}
---This is an internal global function used to send serialized messages to the Pinnacle server.
---@param data Msg
function SendMsg(data)
local encoded = msgpack.encode(data)
@ -92,6 +93,7 @@ function pinnacle.setup(config_func)
socket.send(socket_fd, encoded)
end
---This is an internal global function used to send requests to the Pinnacle server for information.
---@param data Request
function SendRequest(data)
SendMsg({
@ -99,6 +101,8 @@ function pinnacle.setup(config_func)
})
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()
local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4)
assert(msg_len_bytes)

View file

@ -31,7 +31,7 @@ local function new_tag(props)
end
---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()
SendRequest({
GetTagActive = {
@ -44,6 +44,8 @@ function tg:active()
return active
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()
SendRequest({
GetTagName = {
@ -56,10 +58,32 @@ function tg:name()
return name
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.
---@param layout Layout
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
-----------------------------------------------------------
@ -68,12 +92,14 @@ end
---
---If you need to add the names as a table, use `tag.add_table` instead.
---
---You can also do `op:add_tags(...)`.
---
---### Example
---
---```lua
---local output = output.get_by_name("DP-1")
---if output ~= nil then
--- tag.add(output, "1", "2", "3", "4", "5") -- Add tags with names 1-5
---local op = output.get_by_name("DP-1")
---if op ~= nil then
--- tag.add(op, "1", "2", "3", "4", "5") -- Add tags with names 1-5
---end
---```
---@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
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

View file

@ -110,12 +110,12 @@ pub enum Request {
GetWindowClass { window_id: WindowId },
GetWindowTitle { window_id: WindowId },
// Outputs
GetOutputByName { output_name: String },
GetOutputsByModel { model: String },
GetOutputsByRes { res: (u32, u32) },
GetOutputByFocus,
GetOutputs,
GetOutputProps { output_name: String },
// Tags
GetTagsByOutput { output_name: String },
GetTagsByName { tag_name: String },
GetTagOutput { tag_id: TagId },
GetTagActive { tag_id: TagId },
GetTagName { tag_id: TagId },
}
@ -196,16 +196,56 @@ pub enum Args {
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum RequestResponse {
Window { window_id: Option<WindowId> },
Windows { window_ids: Vec<WindowId> },
WindowSize { size: Option<(i32, i32)> },
WindowLocation { loc: Option<(i32, i32)> },
WindowClass { class: Option<String> },
WindowTitle { title: Option<String> },
WindowFloating { floating: Option<bool> },
Output { output_name: Option<String> },
Outputs { output_names: Vec<String> },
Tags { tag_ids: Vec<TagId> },
TagActive { active: bool },
TagName { name: String },
Window {
window_id: Option<WindowId>,
},
Windows {
window_ids: Vec<WindowId>,
},
WindowSize {
size: Option<(i32, i32)>,
},
WindowLocation {
loc: Option<(i32, i32)>,
},
WindowClass {
class: Option<String>,
},
WindowTitle {
title: Option<String>,
},
WindowFloating {
floating: Option<bool>,
},
Output {
output_name: Option<String>,
},
Outputs {
output_names: Vec<String>,
},
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");
}
Request::GetOutputByName { output_name } => {
// TODO: name better
let name = self
Request::GetOutputs => {
let output_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::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,
},
response: RequestResponse::Outputs { output_names },
},
)
.expect("failed to send to client");
}
Request::GetOutputsByRes { res } => {
let names = self
Request::GetOutputProps { output_name } => {
let output = 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
.find(|output| output.name() == output_name);
let res = output.as_ref().and_then(|output| {
output.current_mode().map(|mode| (mode.size.w, mode.size.h))
});
let refresh_rate = output
.as_ref()
.and_then(|output| output.current_mode().map(|mode| mode.refresh));
let model = output
.as_ref()
.map(|output| output.physical_properties().model);
let physical_size = output.as_ref().map(|output| {
(
output.physical_properties().size.w,
output.physical_properties().size.h,
)
});
let make = output
.as_ref()
.map(|output| output.physical_properties().make);
let loc = output
.as_ref()
.map(|output| (output.current_location().x, output.current_location().y));
let focused = self
.focus_state
.focused_output
.as_ref()
.map(|output| output.name());
.and_then(|foc_op| output.map(|op| op == foc_op));
crate::api::send_to_client(
&mut stream,
&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");
@ -558,39 +547,54 @@ impl<B: Backend> State<B> {
.expect("failed to send to client");
}
}
Request::GetTagActive { tag_id } => {
let tag = self
Request::GetTagsByName { tag_name } => {
let tag_ids = 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");
}
.filter(|tag| tag.name() == tag_name)
.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::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 } => {
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");
}
let name = tag_id.tag(self).map(|tag| tag.name());
crate::api::send_to_client(
&mut stream,
&OutgoingMsg::RequestResponse {
response: RequestResponse::TagName { name },
},
)
.expect("failed to send to client");
}
}
}
@ -752,7 +756,7 @@ impl<B: Backend> State<B> {
state
.tags
.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()

View file

@ -28,6 +28,14 @@ impl TagId {
fn next() -> Self {
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)]
@ -88,13 +96,11 @@ impl Tag {
layout: Layout::MasterStack, // TODO: get from config
})))
}
}
impl<B: Backend> State<B> {
pub fn output_for_tag(&self, tag: &Tag) -> Option<Output> {
self.space
pub fn output<B: Backend>(&self, state: &State<B>) -> Option<Output> {
state
.space
.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()
}
}