mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-30 20:34:49 +01:00
Finish Lua setup and setup_locs
This commit is contained in:
parent
3f5a56c201
commit
f6ad192c86
6 changed files with 711 additions and 521 deletions
|
@ -76,12 +76,19 @@ require("pinnacle").setup(function(Pinnacle)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--------------------
|
----------------------
|
||||||
-- Tags --
|
-- Tags and Outputs --
|
||||||
--------------------
|
----------------------
|
||||||
|
|
||||||
local tag_names = { "1", "2", "3", "4", "5" }
|
local tag_names = { "1", "2", "3", "4", "5" }
|
||||||
|
|
||||||
|
-- Setup outputs.
|
||||||
|
--
|
||||||
|
-- `Output.setup` allows you to declare things like mode, scale, and tags for outputs.
|
||||||
|
-- Here we give all outputs tags 1 through 5.
|
||||||
|
--
|
||||||
|
-- Note that output matching functions currently don't infer the type of the parameter,
|
||||||
|
-- so you may need to add `---@param <param name> OutputHandle` above it.
|
||||||
Output.setup({
|
Output.setup({
|
||||||
{
|
{
|
||||||
function(_)
|
function(_)
|
||||||
|
@ -89,23 +96,13 @@ require("pinnacle").setup(function(Pinnacle)
|
||||||
end,
|
end,
|
||||||
tag_names = tag_names,
|
tag_names = tag_names,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"DP-2",
|
|
||||||
scale = 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Pinnacle Window",
|
|
||||||
scale = 0.5,
|
|
||||||
loc = { x = 300, y = 450 },
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
-- `connect_for_all` is useful for performing setup on every monitor you have.
|
-- If you want to declare output locations as well, you can use `Output.setup_locs`.
|
||||||
-- Here, we add tags with names 1-5 and set tag 1 as active.
|
-- This will additionally allow you to recalculate output locations on signals like
|
||||||
-- Output.connect_for_all(function(op)
|
-- output connect, disconnect, and resize.
|
||||||
-- local tags = Tag.add(op, tag_names)
|
--
|
||||||
-- tags[1]:set_active(true)
|
-- Read the admittedly scuffed docs for more.
|
||||||
-- end)
|
|
||||||
|
|
||||||
-- Tag keybinds
|
-- Tag keybinds
|
||||||
for _, tag_name in ipairs(tag_names) do
|
for _, tag_name in ipairs(tag_names) do
|
||||||
|
|
|
@ -166,7 +166,7 @@ function output.connect_for_all(callback)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class OutputSetupArgs
|
---@class OutputSetup
|
||||||
---@field [1] (string | fun(output: OutputHandle): boolean)
|
---@field [1] (string | fun(output: OutputHandle): boolean)
|
||||||
---@field loc ({ x: integer, y: integer } | { [1]: (string | fun(output: OutputHandle): boolean), [2]: Alignment })?
|
---@field loc ({ x: integer, y: integer } | { [1]: (string | fun(output: OutputHandle): boolean), [2]: Alignment })?
|
||||||
---@field mode Mode?
|
---@field mode Mode?
|
||||||
|
@ -184,43 +184,132 @@ end
|
||||||
---Declaratively setup outputs.
|
---Declaratively setup outputs.
|
||||||
---
|
---
|
||||||
---`Output.setup` allows you to specify output properties that will be applied immediately and
|
---`Output.setup` allows you to specify output properties that will be applied immediately and
|
||||||
---on output connection. These include location, mode, scale, and tags.
|
---on output connection. These include mode, scale, tags, and more.
|
||||||
---
|
---
|
||||||
---Arguments will be applied from top to bottom.
|
---Setups will be applied from top to bottom.
|
||||||
---
|
---
|
||||||
---`loc` will not be applied to arguments with an output matching function.
|
---`setups` is an array of `OutputSetup` tables.
|
||||||
|
---The table entry at [1] in an `OutputSetup` table should be either a string or a function
|
||||||
|
---that takes in an `OutputHandle` and returns a boolean. Strings will match output names directly,
|
||||||
|
---while the function matches outputs based on custom logic. You can specify keys such as
|
||||||
|
---`tag_names`, `scale`, and others to customize output properties.
|
||||||
---
|
---
|
||||||
---@param setup OutputSetupArgs[]
|
---### Example
|
||||||
function output.setup(setup)
|
---```lua
|
||||||
|
---Output.setup({
|
||||||
|
--- -- Give all outputs tags 1 through 5
|
||||||
|
--- {
|
||||||
|
--- function(_) return true end,
|
||||||
|
--- tag_names = { "1", "2", "3", "4", "5" },
|
||||||
|
--- }
|
||||||
|
--- -- Give outputs with a preferred mode of 4K a scale of 2.0
|
||||||
|
--- {
|
||||||
|
--- function(op)
|
||||||
|
--- return op:preferred_mode().pixel_width == 2160
|
||||||
|
--- end,
|
||||||
|
--- scale = 2.0,
|
||||||
|
--- },
|
||||||
|
--- -- Additionally give eDP-1 tags 6 and 7
|
||||||
|
--- {
|
||||||
|
--- "eDP-1",
|
||||||
|
--- tag_names = { "6", "7" },
|
||||||
|
--- },
|
||||||
|
---})
|
||||||
|
---```
|
||||||
|
---
|
||||||
|
---@param setups OutputSetup[]
|
||||||
|
function output.setup(setups)
|
||||||
---@param op OutputHandle
|
---@param op OutputHandle
|
||||||
local function apply_transformers(op)
|
local function apply_setups(op)
|
||||||
for _, args in ipairs(setup) do
|
for _, setup in ipairs(setups) do
|
||||||
if output_matches(op, args[1]) then
|
if output_matches(op, setup[1]) then
|
||||||
if args.mode then
|
if setup.mode then
|
||||||
op:set_mode(args.mode.pixel_width, args.mode.pixel_height, args.mode.refresh_rate_millihz)
|
op:set_mode(setup.mode.pixel_width, setup.mode.pixel_height, setup.mode.refresh_rate_millihz)
|
||||||
end
|
end
|
||||||
if args.scale then
|
if setup.scale then
|
||||||
op:set_scale(args.scale)
|
op:set_scale(setup.scale)
|
||||||
end
|
end
|
||||||
if args.tag_names then
|
if setup.tag_names then
|
||||||
local tags = require("pinnacle.tag").add(op, args.tag_names)
|
require("pinnacle.tag").add(op, setup.tag_names)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tags = op:tags() or {}
|
||||||
|
if tags[1] then
|
||||||
tags[1]:set_active(true)
|
tags[1]:set_active(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply mode, scale, and transforms first
|
output.connect_for_all(function(op)
|
||||||
local outputs = output.get_all()
|
apply_setups(op)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
for _, op in ipairs(outputs) do
|
---@alias OutputLoc
|
||||||
apply_transformers(op)
|
---| { x: integer, y: integer } -- A specific point
|
||||||
|
---| { [1]: string, [2]: Alignment } -- A location relative to another output
|
||||||
|
---| { [1]: string, [2]: Alignment }[] -- A location relative to another output with fallbacks
|
||||||
|
|
||||||
|
---@alias UpdateLocsOn
|
||||||
|
---| "connect" -- Update output locations on output connect
|
||||||
|
---| "disconnect" -- Update output locations on output disconnect
|
||||||
|
---| "resize" -- Update output locations on output resize
|
||||||
|
|
||||||
|
---Setup locations for outputs.
|
||||||
|
---
|
||||||
|
---This function lets you declare positions for outputs, either as a specific point in the global
|
||||||
|
---space or relative to another output.
|
||||||
|
---
|
||||||
|
---`update_locs_on` specifies when output positions should be recomputed. It can be `"all"`, signaling you
|
||||||
|
---want positions to update on all of output connect, disconnect, and resize, or it can be a table
|
||||||
|
---containing `"connect"`, `"disconnect"`, and/or `"resize"`.
|
||||||
|
---
|
||||||
|
---`setup` is an array of tables of the form `{ [1]: string, loc: OutputLoc }`, where `OutputLoc` is either
|
||||||
|
---the table `{ x: integer, y: integer }`, `{ [1]: string, [2]: Alignment }`, or an array of the latter table.
|
||||||
|
---See the examples for information.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
--- -- vvvvv Relayout on output connect, disconnect, and resize
|
||||||
|
---Output.setup_locs("all", {
|
||||||
|
--- -- Anchor eDP-1 to (0, 0) so we can place other outputs relative to it
|
||||||
|
--- { "eDP-1", loc = { x = 0, y = 0 } },
|
||||||
|
--- -- Place HDMI-A-1 below it centered
|
||||||
|
--- { "HDMI-A-1", loc = { "eDP-1", "bottom_align_center" } },
|
||||||
|
--- -- Place HDMI-A-2 below HDMI-A-1.
|
||||||
|
--- -- Additionally, if HDMI-A-1 isn't connected, fallback to placing
|
||||||
|
--- -- it below eDP-1 instead.
|
||||||
|
--- {
|
||||||
|
--- "HDMI-A-2",
|
||||||
|
--- loc = {
|
||||||
|
--- { "HDMI-A-1", "bottom_align_center" },
|
||||||
|
--- { "eDP-1", "bottom_align_center" },
|
||||||
|
--- },
|
||||||
|
--- },
|
||||||
|
---})
|
||||||
|
---
|
||||||
|
--- -- Only relayout on output connect and resize
|
||||||
|
---Output.setup_locs({ "connect", "resize" }, { ... })
|
||||||
|
---```
|
||||||
|
---
|
||||||
|
---@param update_locs_on (UpdateLocsOn)[] | "all"
|
||||||
|
---@param setup { [1]: string, loc: OutputLoc }[]
|
||||||
|
function output.setup_locs(update_locs_on, setup)
|
||||||
|
---@type { [1]: string, loc: ({ x: integer, y: integer } | { [1]: string, [2]: Alignment }[]) }[]
|
||||||
|
local setups = {}
|
||||||
|
for _, s in ipairs(setup) do
|
||||||
|
if type(s.loc[1]) == "string" then
|
||||||
|
table.insert(setups, { s[1], loc = { s.loc } })
|
||||||
|
else
|
||||||
|
table.insert(setups, s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function layout_outputs()
|
local function layout_outputs()
|
||||||
local outputs = output.get_all()
|
local outputs = output.get_all()
|
||||||
|
|
||||||
---@type table<string, { x: integer, y: integer }>
|
---@type OutputHandle[]
|
||||||
local placed_outputs = {}
|
local placed_outputs = {}
|
||||||
|
|
||||||
local rightmost_output = {
|
local rightmost_output = {
|
||||||
|
@ -228,16 +317,15 @@ function output.setup(setup)
|
||||||
x = nil,
|
x = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
local relative_to_outputs_that_are_not_placed = {}
|
|
||||||
|
|
||||||
-- Place outputs with a specified location first
|
-- Place outputs with a specified location first
|
||||||
for _, args in ipairs(setup) do
|
---@diagnostic disable-next-line: redefined-local
|
||||||
|
for _, setup in ipairs(setups) do
|
||||||
for _, op in ipairs(outputs) do
|
for _, op in ipairs(outputs) do
|
||||||
if type(args[1]) == "string" and op.name == args[1] then
|
if op.name == setup[1] then
|
||||||
if args.loc and args.loc.x and args.loc.y then
|
if setup.loc and setup.loc.x and setup.loc.y then
|
||||||
local loc = { x = args.loc.x, y = args.loc.y }
|
local loc = { x = setup.loc.x, y = setup.loc.y }
|
||||||
op:set_location(loc)
|
op:set_location(loc)
|
||||||
placed_outputs[op.name] = loc
|
table.insert(placed_outputs, op)
|
||||||
|
|
||||||
local props = op:props()
|
local props = op:props()
|
||||||
if not rightmost_output.x or rightmost_output.x < props.x + props.logical_width then
|
if not rightmost_output.x or rightmost_output.x < props.x + props.logical_width then
|
||||||
|
@ -245,107 +333,73 @@ function output.setup(setup)
|
||||||
rightmost_output.x = props.x + props.logical_width
|
rightmost_output.x = props.x + props.logical_width
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Place outputs with no specified location in a line to the rightmost
|
|
||||||
for _, op in ipairs(outputs) do
|
|
||||||
local args_contains_op = false
|
|
||||||
|
|
||||||
for _, args in ipairs(setup) do
|
|
||||||
if type(args[1]) == "string" and op.name == args[1] then
|
|
||||||
args_contains_op = true
|
|
||||||
if not args.loc then
|
|
||||||
if not rightmost_output.output then
|
|
||||||
op:set_location({ x = 0, y = 0 })
|
|
||||||
else
|
|
||||||
op:set_loc_adj_to(rightmost_output.output, "right_align_top")
|
|
||||||
end
|
|
||||||
|
|
||||||
local props = op:props()
|
|
||||||
|
|
||||||
local loc = { x = props.x, y = props.y }
|
|
||||||
rightmost_output.output = op
|
|
||||||
rightmost_output.x = props.x
|
|
||||||
|
|
||||||
placed_outputs[op.name] = loc
|
|
||||||
|
|
||||||
goto continue_outer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- No match, still lay it out
|
|
||||||
|
|
||||||
if not args_contains_op and not placed_outputs[op.name] then
|
|
||||||
if not rightmost_output.output then
|
|
||||||
op:set_location({ x = 0, y = 0 })
|
|
||||||
else
|
|
||||||
op:set_loc_adj_to(rightmost_output.output, "right_align_top")
|
|
||||||
end
|
|
||||||
|
|
||||||
local props = op:props()
|
|
||||||
|
|
||||||
local loc = { x = props.x, y = props.y }
|
|
||||||
rightmost_output.output = op
|
|
||||||
rightmost_output.x = props.x
|
|
||||||
|
|
||||||
placed_outputs[op.name] = loc
|
|
||||||
end
|
|
||||||
|
|
||||||
::continue_outer::
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Place outputs that are relative to other outputs
|
|
||||||
for _, args in ipairs(setup) do
|
|
||||||
for _, op in ipairs(outputs) do
|
|
||||||
if type(args[1]) == "string" and op.name == args[1] then
|
|
||||||
if args.loc and args.loc[1] and args.loc[2] then
|
|
||||||
local matcher = args.loc[1]
|
|
||||||
local alignment = args.loc[2]
|
|
||||||
---@type OutputHandle?
|
|
||||||
local relative_to = nil
|
|
||||||
|
|
||||||
for _, op in ipairs(outputs) do
|
|
||||||
if output_matches(op, matcher) then
|
|
||||||
relative_to = op
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not relative_to then
|
-- Place outputs that are relative to other outputs
|
||||||
table.insert(relative_to_outputs_that_are_not_placed, op)
|
local function next_output_with_relative_to()
|
||||||
|
---@diagnostic disable-next-line: redefined-local
|
||||||
|
for _, setup in ipairs(setups) do
|
||||||
|
for _, op in ipairs(outputs) do
|
||||||
|
for _, placed_op in ipairs(placed_outputs) do
|
||||||
|
if placed_op.name == op.name then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if op.name ~= setup[1] or type(setup.loc[1]) ~= "table" then
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
if not placed_outputs[relative_to.name] then
|
for _, loc in ipairs(setup.loc) do
|
||||||
-- The output it's relative to hasn't been placed yet;
|
local relative_to_name = loc[1]
|
||||||
-- Users must place outputs before ones being placed relative to them
|
local alignment = loc[2]
|
||||||
table.insert(relative_to_outputs_that_are_not_placed, op)
|
for _, placed_op in ipairs(placed_outputs) do
|
||||||
goto continue
|
if placed_op.name == relative_to_name then
|
||||||
|
return op, placed_op, alignment
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
goto continue_outer
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
::continue_outer::
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local op, relative_to, alignment = next_output_with_relative_to()
|
||||||
|
if not op then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
---@cast relative_to OutputHandle
|
||||||
|
---@cast alignment Alignment
|
||||||
|
|
||||||
op:set_loc_adj_to(relative_to, alignment)
|
op:set_loc_adj_to(relative_to, alignment)
|
||||||
|
table.insert(placed_outputs, op)
|
||||||
|
|
||||||
local props = op:props()
|
local props = op:props()
|
||||||
|
if not rightmost_output.x or rightmost_output.x < props.x + props.logical_width then
|
||||||
local loc = { x = props.x, y = props.y }
|
|
||||||
|
|
||||||
if not rightmost_output.output or rightmost_output.x < props.x + props.logical_width then
|
|
||||||
rightmost_output.output = op
|
rightmost_output.output = op
|
||||||
rightmost_output.x = props.x + props.logical_width
|
rightmost_output.x = props.x + props.logical_width
|
||||||
end
|
end
|
||||||
|
|
||||||
placed_outputs[op.name] = loc
|
|
||||||
end
|
|
||||||
end
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Place still-not-placed outputs
|
-- Place still-not-placed outputs
|
||||||
for _, op in ipairs(relative_to_outputs_that_are_not_placed) do
|
for _, op in ipairs(outputs) do
|
||||||
|
for _, placed_op in ipairs(placed_outputs) do
|
||||||
|
if placed_op.name == op.name then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not rightmost_output.output then
|
if not rightmost_output.output then
|
||||||
op:set_location({ x = 0, y = 0 })
|
op:set_location({ x = 0, y = 0 })
|
||||||
else
|
else
|
||||||
|
@ -354,30 +408,62 @@ function output.setup(setup)
|
||||||
|
|
||||||
local props = op:props()
|
local props = op:props()
|
||||||
|
|
||||||
local loc = { x = props.x, y = props.y }
|
|
||||||
rightmost_output.output = op
|
rightmost_output.output = op
|
||||||
rightmost_output.x = props.x
|
rightmost_output.x = props.x
|
||||||
|
|
||||||
placed_outputs[op.name] = loc
|
table.insert(placed_outputs, op)
|
||||||
|
|
||||||
|
::continue::
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
layout_outputs()
|
layout_outputs()
|
||||||
|
|
||||||
|
local layout_on_connect = false
|
||||||
|
local layout_on_disconnect = false
|
||||||
|
local layout_on_resize = false
|
||||||
|
|
||||||
|
if update_locs_on == "all" then
|
||||||
|
layout_on_connect = true
|
||||||
|
layout_on_disconnect = true
|
||||||
|
layout_on_resize = true
|
||||||
|
else
|
||||||
|
---@cast update_locs_on UpdateLocsOn[]
|
||||||
|
|
||||||
|
for _, update_on in ipairs(update_locs_on) do
|
||||||
|
if update_on == "connect" then
|
||||||
|
layout_on_connect = true
|
||||||
|
elseif update_on == "disconnect" then
|
||||||
|
layout_on_disconnect = true
|
||||||
|
elseif update_on == "resize" then
|
||||||
|
layout_on_resize = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if layout_on_connect then
|
||||||
|
-- FIXME: This currently does not duplicate tags because the connect signal does not fire for
|
||||||
|
-- | previously connected outputs. However, this is unintended behavior, so fix this when you fix that.
|
||||||
output.connect_signal({
|
output.connect_signal({
|
||||||
connect = function(op)
|
connect = function(_)
|
||||||
-- FIXME: This currently does not duplicate tags because the connect signal does not fire for previously connected
|
|
||||||
-- | outputs. However, this is unintended behavior, so fix this when you fix that.
|
|
||||||
apply_transformers(op)
|
|
||||||
layout_outputs()
|
|
||||||
end,
|
|
||||||
disconnect = function(_)
|
|
||||||
layout_outputs()
|
|
||||||
end,
|
|
||||||
resize = function(_, _, _)
|
|
||||||
layout_outputs()
|
layout_outputs()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
end
|
||||||
|
if layout_on_disconnect then
|
||||||
|
output.connect_signal({
|
||||||
|
disconnect = function(_)
|
||||||
|
layout_outputs()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if layout_on_resize then
|
||||||
|
output.connect_signal({
|
||||||
|
resize = function(_)
|
||||||
|
layout_outputs()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type table<string, SignalServiceMethod>
|
---@type table<string, SignalServiceMethod>
|
||||||
|
|
|
@ -369,9 +369,7 @@ impl Output {
|
||||||
|
|
||||||
if update_locs_on.contains(UpdateLocsOn::CONNECT) {
|
if update_locs_on.contains(UpdateLocsOn::CONNECT) {
|
||||||
self.connect_signal(OutputSignal::Connect(Box::new(move |output| {
|
self.connect_signal(OutputSignal::Connect(Box::new(move |output| {
|
||||||
println!("GOT CONNECT SIGNAL FOR {}", output.name());
|
|
||||||
layout_outputs_clone2();
|
layout_outputs_clone2();
|
||||||
println!("FINISHED CONNECT SIGNAL FOR {}", output.name());
|
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,6 @@ macro_rules! signals {
|
||||||
|
|
||||||
callback_sender
|
callback_sender
|
||||||
.send((self.current_id, callback))
|
.send((self.current_id, callback))
|
||||||
.map_err(|e| { println!("{e}"); e })
|
|
||||||
.expect("failed to send callback");
|
.expect("failed to send callback");
|
||||||
|
|
||||||
let handle = SignalHandle::new(self.current_id, remove_callback_sender);
|
let handle = SignalHandle::new(self.current_id, remove_callback_sender);
|
||||||
|
|
268
tests/lua_api.rs
268
tests/lua_api.rs
|
@ -5,7 +5,7 @@ use std::{
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{sleep_secs, test_api, with_state};
|
use crate::common::{output_for_name, sleep_secs, test_api, with_state};
|
||||||
|
|
||||||
use pinnacle::state::WithState;
|
use pinnacle::state::WithState;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
@ -99,20 +99,17 @@ macro_rules! setup_lua {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mod coverage {
|
use pinnacle::{
|
||||||
use pinnacle::{
|
|
||||||
tag::TagId,
|
tag::TagId,
|
||||||
window::{
|
window::{
|
||||||
rules::{WindowRule, WindowRuleCondition},
|
rules::{WindowRule, WindowRuleCondition},
|
||||||
window_state::FullscreenOrMaximized,
|
window_state::FullscreenOrMaximized,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
// Process
|
||||||
|
|
||||||
// Process
|
mod process {
|
||||||
|
|
||||||
mod process {
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -151,11 +148,11 @@ mod coverage {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
|
|
||||||
mod window {
|
mod window {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -367,9 +364,9 @@ mod coverage {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod tag {
|
mod tag {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
mod handle {
|
mod handle {
|
||||||
|
@ -416,16 +413,16 @@ mod coverage {
|
||||||
assert(potato_props.name == "Potato")
|
assert(potato_props.name == "Potato")
|
||||||
assert(potato_props.output.name == "Pinnacle Window")
|
assert(potato_props.output.name == "Pinnacle Window")
|
||||||
assert(#potato_props.windows == 2)
|
assert(#potato_props.windows == 2)
|
||||||
assert(first_props.windows[1]:class() == "foot")
|
assert(potato_props.windows[1]:class() == "foot")
|
||||||
assert(first_props.windows[2]:class() == "foot")
|
assert(potato_props.windows[2]:class() == "foot")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod output {
|
mod output {
|
||||||
use smithay::utils::Rectangle;
|
use smithay::{output::Output, utils::Rectangle};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -439,113 +436,226 @@ mod coverage {
|
||||||
function(_)
|
function(_)
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
tag_names = { "First", "Third", "Schmurd" },
|
tag_names = { "1", "2", "3" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Pinnacle Window",
|
function(op)
|
||||||
loc = { x = 300, y = 0 },
|
return string.match(op.name, "Test") ~= nil
|
||||||
|
end,
|
||||||
|
tag_names = { "Test 4", "Test 5" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Output 1",
|
"Second",
|
||||||
loc = { "Pinnacle Window", "bottom_align_left" },
|
scale = 2.0,
|
||||||
}
|
mode = {
|
||||||
|
pixel_width = 6900,
|
||||||
|
pixel_height = 420,
|
||||||
|
refresh_rate_millihz = 69420,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep_secs(1);
|
sleep_secs(1);
|
||||||
|
|
||||||
with_state(&sender, |state| {
|
with_state(&sender, |state| {
|
||||||
state.new_output("Output 1", (960, 540).into());
|
state.new_output("First", (300, 200).into());
|
||||||
|
state.new_output("Second", (300, 200).into());
|
||||||
|
state.new_output("Test Third", (300, 200).into());
|
||||||
});
|
});
|
||||||
|
|
||||||
sleep_secs(1);
|
sleep_secs(1);
|
||||||
|
|
||||||
with_state(&sender, |state| {
|
with_state(&sender, |state| {
|
||||||
let original_op = state
|
let original_op = output_for_name(state, "Pinnacle Window");
|
||||||
.space
|
let first_op = output_for_name(state, "First");
|
||||||
.outputs()
|
let second_op = output_for_name(state, "Second");
|
||||||
.find(|op| op.name() == "Pinnacle Window")
|
let test_third_op = output_for_name(state, "Test Third");
|
||||||
.unwrap();
|
|
||||||
let output_1 = state
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.find(|op| op.name() == "Output 1")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let original_op_geo = state.space.output_geometry(original_op).unwrap();
|
let tags_for = |output: &Output| {
|
||||||
let output_1_geo = state.space.output_geometry(output_1).unwrap();
|
output
|
||||||
|
.with_state(|state| state.tags.iter().map(|t| t.name()).collect::<Vec<_>>())
|
||||||
|
};
|
||||||
|
|
||||||
|
let focused_tags_for = |output: &Output| {
|
||||||
|
output.with_state(|state| {
|
||||||
|
state.focused_tags().map(|t| t.name()).collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tags_for(&original_op), vec!["1", "2", "3"]);
|
||||||
|
assert_eq!(tags_for(&first_op), vec!["1", "2", "3"]);
|
||||||
|
assert_eq!(tags_for(&second_op), vec!["1", "2", "3"]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
original_op_geo,
|
tags_for(&test_third_op),
|
||||||
Rectangle::from_loc_and_size((300, 0), (1920, 1080))
|
vec!["1", "2", "3", "Test 4", "Test 5"]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(focused_tags_for(&original_op), vec!["1"]);
|
||||||
output_1_geo,
|
assert_eq!(focused_tags_for(&test_third_op), vec!["1"]);
|
||||||
Rectangle::from_loc_and_size((300, 1080), (960, 540))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(second_op.current_scale().fractional_scale(), 2.0);
|
||||||
output_1.with_state(|state| state
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.map(|tag| tag.name())
|
|
||||||
.collect::<Vec<_>>()),
|
|
||||||
vec!["First", "Third", "Schmurd"]
|
|
||||||
);
|
|
||||||
|
|
||||||
state.remove_output(&original_op.clone());
|
let second_mode = second_op.current_mode().unwrap();
|
||||||
|
assert_eq!(second_mode.size.w, 6900);
|
||||||
|
assert_eq!(second_mode.size.h, 420);
|
||||||
|
assert_eq!(second_mode.refresh, 69420);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
#[self::test]
|
||||||
|
async fn setup_loc_with_cyclic_relative_locs_works() -> anyhow::Result<()> {
|
||||||
|
test_api(|sender| {
|
||||||
|
setup_lua! { |Pinnacle|
|
||||||
|
Pinnacle.output.setup_locs("all", {
|
||||||
|
{ "Pinnacle Window", loc = { x = 0, y = 0 } },
|
||||||
|
{ "First", loc = { "Second", "left_align_top" } },
|
||||||
|
{ "Second", loc = { "First", "right_align_top" } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep_secs(1);
|
||||||
|
|
||||||
|
with_state(&sender, |state| {
|
||||||
|
state.new_output("First", (300, 200).into());
|
||||||
});
|
});
|
||||||
|
|
||||||
sleep_secs(1);
|
sleep_secs(1);
|
||||||
|
|
||||||
with_state(&sender, |state| {
|
with_state(&sender, |state| {
|
||||||
let output_1 = state
|
let original_op = output_for_name(state, "Pinnacle Window");
|
||||||
.space
|
let first_op = output_for_name(state, "First");
|
||||||
.outputs()
|
|
||||||
.find(|op| op.name() == "Output 1")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let output_1_geo = state.space.output_geometry(output_1).unwrap();
|
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||||
|
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
output_1_geo,
|
original_geo,
|
||||||
Rectangle::from_loc_and_size((0, 0), (960, 540))
|
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
first_geo,
|
||||||
|
Rectangle::from_loc_and_size((1920, 0), (300, 200))
|
||||||
);
|
);
|
||||||
|
|
||||||
state.new_output("Output 2", (300, 500).into());
|
state.new_output("Second", (500, 500).into());
|
||||||
});
|
});
|
||||||
|
|
||||||
sleep_secs(1);
|
sleep_secs(1);
|
||||||
|
|
||||||
with_state(&sender, |state| {
|
with_state(&sender, |state| {
|
||||||
let output_1 = state
|
let original_op = output_for_name(state, "Pinnacle Window");
|
||||||
.space
|
let first_op = output_for_name(state, "First");
|
||||||
.outputs()
|
let second_op = output_for_name(state, "Second");
|
||||||
.find(|op| op.name() == "Output 1")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let output_2 = state
|
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||||
.space
|
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||||
.outputs()
|
let second_geo = state.space.output_geometry(&second_op).unwrap();
|
||||||
.find(|op| op.name() == "Output 2")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let output_1_geo = state.space.output_geometry(output_1).unwrap();
|
|
||||||
let output_2_geo = state.space.output_geometry(output_2).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
output_2_geo,
|
original_geo,
|
||||||
Rectangle::from_loc_and_size((0, 0), (300, 500))
|
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
output_1_geo,
|
first_geo,
|
||||||
Rectangle::from_loc_and_size((300, 0), (960, 540))
|
Rectangle::from_loc_and_size((1920, 0), (300, 200))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_geo,
|
||||||
|
Rectangle::from_loc_and_size((1920 + 300, 0), (500, 500))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
#[self::test]
|
||||||
|
async fn setup_loc_with_relative_locs_with_more_than_one_relative_works() -> anyhow::Result<()>
|
||||||
|
{
|
||||||
|
test_api(|sender| {
|
||||||
|
setup_lua! { |Pinnacle|
|
||||||
|
Pinnacle.output.setup_locs("all", {
|
||||||
|
{ "Pinnacle Window", loc = { x = 0, y = 0 } },
|
||||||
|
{ "First", loc = { "Pinnacle Window", "bottom_align_left" } },
|
||||||
|
{ "Second", loc = { "First", "bottom_align_left" } },
|
||||||
|
{
|
||||||
|
"Third",
|
||||||
|
loc = {
|
||||||
|
{ "Second", "bottom_align_left" },
|
||||||
|
{ "First", "bottom_align_left" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep_secs(1);
|
||||||
|
|
||||||
|
with_state(&sender, |state| {
|
||||||
|
state.new_output("First", (300, 200).into());
|
||||||
|
state.new_output("Second", (300, 700).into());
|
||||||
|
state.new_output("Third", (300, 400).into());
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep_secs(1);
|
||||||
|
|
||||||
|
with_state(&sender, |state| {
|
||||||
|
let original_op = output_for_name(state, "Pinnacle Window");
|
||||||
|
let first_op = output_for_name(state, "First");
|
||||||
|
let second_op = output_for_name(state, "Second");
|
||||||
|
let third_op = output_for_name(state, "Third");
|
||||||
|
|
||||||
|
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||||
|
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||||
|
let second_geo = state.space.output_geometry(&second_op).unwrap();
|
||||||
|
let third_geo = state.space.output_geometry(&third_op).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
original_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
first_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 1080), (300, 200))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 1080 + 200), (300, 700))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
third_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 1080 + 200 + 700), (300, 400))
|
||||||
|
);
|
||||||
|
|
||||||
|
state.remove_output(&second_op);
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep_secs(1);
|
||||||
|
|
||||||
|
with_state(&sender, |state| {
|
||||||
|
let original_op = output_for_name(state, "Pinnacle Window");
|
||||||
|
let first_op = output_for_name(state, "First");
|
||||||
|
let third_op = output_for_name(state, "Third");
|
||||||
|
|
||||||
|
let original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||||
|
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||||
|
let third_geo = state.space.output_geometry(&third_op).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
original_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
first_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 1080), (300, 200))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
third_geo,
|
||||||
|
Rectangle::from_loc_and_size((0, 1080 + 200), (300, 400))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ mod output {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
#[self::test]
|
#[self::test]
|
||||||
async fn setup_loc_with_cyclic_relative_locs_work() -> anyhow::Result<()> {
|
async fn setup_loc_with_cyclic_relative_locs_works() -> anyhow::Result<()> {
|
||||||
test_api(|sender| {
|
test_api(|sender| {
|
||||||
setup_rust(|api| {
|
setup_rust(|api| {
|
||||||
api.output.setup_locs(
|
api.output.setup_locs(
|
||||||
|
|
Loading…
Add table
Reference in a new issue