mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-18 22:26:12 +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)
|
||||
|
||||
--------------------
|
||||
-- Tags --
|
||||
--------------------
|
||||
----------------------
|
||||
-- Tags and Outputs --
|
||||
----------------------
|
||||
|
||||
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({
|
||||
{
|
||||
function(_)
|
||||
|
@ -89,23 +96,13 @@ require("pinnacle").setup(function(Pinnacle)
|
|||
end,
|
||||
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.
|
||||
-- Here, we add tags with names 1-5 and set tag 1 as active.
|
||||
-- Output.connect_for_all(function(op)
|
||||
-- local tags = Tag.add(op, tag_names)
|
||||
-- tags[1]:set_active(true)
|
||||
-- end)
|
||||
-- If you want to declare output locations as well, you can use `Output.setup_locs`.
|
||||
-- This will additionally allow you to recalculate output locations on signals like
|
||||
-- output connect, disconnect, and resize.
|
||||
--
|
||||
-- Read the admittedly scuffed docs for more.
|
||||
|
||||
-- Tag keybinds
|
||||
for _, tag_name in ipairs(tag_names) do
|
||||
|
|
|
@ -166,7 +166,7 @@ function output.connect_for_all(callback)
|
|||
})
|
||||
end
|
||||
|
||||
---@class OutputSetupArgs
|
||||
---@class OutputSetup
|
||||
---@field [1] (string | fun(output: OutputHandle): boolean)
|
||||
---@field loc ({ x: integer, y: integer } | { [1]: (string | fun(output: OutputHandle): boolean), [2]: Alignment })?
|
||||
---@field mode Mode?
|
||||
|
@ -184,43 +184,132 @@ end
|
|||
---Declaratively setup outputs.
|
||||
---
|
||||
---`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[]
|
||||
function output.setup(setup)
|
||||
---### Example
|
||||
---```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
|
||||
local function apply_transformers(op)
|
||||
for _, args in ipairs(setup) do
|
||||
if output_matches(op, args[1]) then
|
||||
if args.mode then
|
||||
op:set_mode(args.mode.pixel_width, args.mode.pixel_height, args.mode.refresh_rate_millihz)
|
||||
local function apply_setups(op)
|
||||
for _, setup in ipairs(setups) do
|
||||
if output_matches(op, setup[1]) then
|
||||
if setup.mode then
|
||||
op:set_mode(setup.mode.pixel_width, setup.mode.pixel_height, setup.mode.refresh_rate_millihz)
|
||||
end
|
||||
if args.scale then
|
||||
op:set_scale(args.scale)
|
||||
if setup.scale then
|
||||
op:set_scale(setup.scale)
|
||||
end
|
||||
if args.tag_names then
|
||||
local tags = require("pinnacle.tag").add(op, args.tag_names)
|
||||
tags[1]:set_active(true)
|
||||
if setup.tag_names then
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply mode, scale, and transforms first
|
||||
local outputs = output.get_all()
|
||||
output.connect_for_all(function(op)
|
||||
apply_setups(op)
|
||||
end)
|
||||
end
|
||||
|
||||
for _, op in ipairs(outputs) do
|
||||
apply_transformers(op)
|
||||
---@alias OutputLoc
|
||||
---| { 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
|
||||
|
||||
local function layout_outputs()
|
||||
local outputs = output.get_all()
|
||||
|
||||
---@type table<string, { x: integer, y: integer }>
|
||||
---@type OutputHandle[]
|
||||
local placed_outputs = {}
|
||||
|
||||
local rightmost_output = {
|
||||
|
@ -228,16 +317,15 @@ function output.setup(setup)
|
|||
x = nil,
|
||||
}
|
||||
|
||||
local relative_to_outputs_that_are_not_placed = {}
|
||||
|
||||
-- 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
|
||||
if type(args[1]) == "string" and op.name == args[1] then
|
||||
if args.loc and args.loc.x and args.loc.y then
|
||||
local loc = { x = args.loc.x, y = args.loc.y }
|
||||
if op.name == setup[1] then
|
||||
if setup.loc and setup.loc.x and setup.loc.y then
|
||||
local loc = { x = setup.loc.x, y = setup.loc.y }
|
||||
op:set_location(loc)
|
||||
placed_outputs[op.name] = loc
|
||||
table.insert(placed_outputs, op)
|
||||
|
||||
local props = op:props()
|
||||
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
|
||||
end
|
||||
end
|
||||
break
|
||||
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
|
||||
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
|
||||
|
||||
for _, op in ipairs(outputs) do
|
||||
if output_matches(op, matcher) then
|
||||
relative_to = op
|
||||
break
|
||||
if op.name ~= setup[1] or type(setup.loc[1]) ~= "table" then
|
||||
goto continue
|
||||
end
|
||||
|
||||
for _, loc in ipairs(setup.loc) do
|
||||
local relative_to_name = loc[1]
|
||||
local alignment = loc[2]
|
||||
for _, placed_op in ipairs(placed_outputs) do
|
||||
if placed_op.name == relative_to_name then
|
||||
return op, placed_op, alignment
|
||||
end
|
||||
end
|
||||
|
||||
if not relative_to then
|
||||
table.insert(relative_to_outputs_that_are_not_placed, op)
|
||||
goto continue
|
||||
end
|
||||
|
||||
if not placed_outputs[relative_to.name] then
|
||||
-- The output it's relative to hasn't been placed yet;
|
||||
-- Users must place outputs before ones being placed relative to them
|
||||
table.insert(relative_to_outputs_that_are_not_placed, op)
|
||||
goto continue
|
||||
end
|
||||
|
||||
op:set_loc_adj_to(relative_to, alignment)
|
||||
|
||||
local props = op:props()
|
||||
|
||||
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.x = props.x + props.logical_width
|
||||
end
|
||||
|
||||
placed_outputs[op.name] = loc
|
||||
end
|
||||
|
||||
goto continue_outer
|
||||
|
||||
::continue::
|
||||
end
|
||||
::continue::
|
||||
::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)
|
||||
table.insert(placed_outputs, op)
|
||||
|
||||
local props = op:props()
|
||||
if not rightmost_output.x or rightmost_output.x < props.x + props.logical_width then
|
||||
rightmost_output.output = op
|
||||
rightmost_output.x = props.x + props.logical_width
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
op:set_location({ x = 0, y = 0 })
|
||||
else
|
||||
|
@ -354,30 +408,62 @@ function output.setup(setup)
|
|||
|
||||
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
|
||||
table.insert(placed_outputs, op)
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
layout_outputs()
|
||||
|
||||
output.connect_signal({
|
||||
connect = function(op)
|
||||
-- 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()
|
||||
end,
|
||||
})
|
||||
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({
|
||||
connect = function(_)
|
||||
layout_outputs()
|
||||
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
|
||||
|
||||
---@type table<string, SignalServiceMethod>
|
||||
|
|
|
@ -369,9 +369,7 @@ impl Output {
|
|||
|
||||
if update_locs_on.contains(UpdateLocsOn::CONNECT) {
|
||||
self.connect_signal(OutputSignal::Connect(Box::new(move |output| {
|
||||
println!("GOT CONNECT SIGNAL FOR {}", output.name());
|
||||
layout_outputs_clone2();
|
||||
println!("FINISHED CONNECT SIGNAL FOR {}", output.name());
|
||||
})));
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ macro_rules! signals {
|
|||
|
||||
callback_sender
|
||||
.send((self.current_id, callback))
|
||||
.map_err(|e| { println!("{e}"); e })
|
||||
.expect("failed to send callback");
|
||||
|
||||
let handle = SignalHandle::new(self.current_id, remove_callback_sender);
|
||||
|
|
846
tests/lua_api.rs
846
tests/lua_api.rs
|
@ -5,7 +5,7 @@ use std::{
|
|||
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 test_log::test;
|
||||
|
@ -99,26 +99,186 @@ macro_rules! setup_lua {
|
|||
};
|
||||
}
|
||||
|
||||
mod coverage {
|
||||
use pinnacle::{
|
||||
tag::TagId,
|
||||
window::{
|
||||
rules::{WindowRule, WindowRuleCondition},
|
||||
window_state::FullscreenOrMaximized,
|
||||
},
|
||||
};
|
||||
use pinnacle::{
|
||||
tag::TagId,
|
||||
window::{
|
||||
rules::{WindowRule, WindowRuleCondition},
|
||||
window_state::FullscreenOrMaximized,
|
||||
},
|
||||
};
|
||||
|
||||
// Process
|
||||
|
||||
mod process {
|
||||
|
||||
use super::*;
|
||||
|
||||
// Process
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn spawn() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
mod process {
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.windows.len(), 1);
|
||||
assert_eq!(state.windows[0].class(), Some("foot".to_string()));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn set_env() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.set_env("PROCESS_SET_ENV", "env value")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |_state| {
|
||||
assert_eq!(
|
||||
std::env::var("PROCESS_SET_ENV"),
|
||||
Ok("env value".to_string())
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Window
|
||||
|
||||
mod window {
|
||||
use super::*;
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn get_all() -> anyhow::Result<()> {
|
||||
test_api(|_sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
assert(#Pinnacle.window.get_all() == 0)
|
||||
|
||||
for i = 1, 5 do
|
||||
Pinnacle.process.spawn("foot")
|
||||
end
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
assert(#Pinnacle.window.get_all() == 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn get_focused() -> anyhow::Result<()> {
|
||||
test_api(|_sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
assert(not Pinnacle.window.get_focused())
|
||||
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "1")[1]:set_active(true)
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
assert(Pinnacle.window.get_focused())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn add_window_rule() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "Tag Name")
|
||||
Pinnacle.window.add_window_rule({
|
||||
cond = { classes = { "firefox" } },
|
||||
rule = { tags = { Pinnacle.tag.get("Tag Name") } },
|
||||
})
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.config.window_rules.len(), 1);
|
||||
assert_eq!(
|
||||
state.config.window_rules[0],
|
||||
(
|
||||
WindowRuleCondition {
|
||||
class: Some(vec!["firefox".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
WindowRule {
|
||||
tags: Some(vec![TagId(0)]),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "Tag Name 2")
|
||||
Pinnacle.window.add_window_rule({
|
||||
cond = {
|
||||
all = {
|
||||
{
|
||||
classes = { "steam" },
|
||||
tags = {
|
||||
Pinnacle.tag.get("Tag Name"),
|
||||
Pinnacle.tag.get("Tag Name 2"),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
rule = { fullscreen_or_maximized = "fullscreen" },
|
||||
})
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.config.window_rules.len(), 2);
|
||||
assert_eq!(
|
||||
state.config.window_rules[1],
|
||||
(
|
||||
WindowRuleCondition {
|
||||
cond_all: Some(vec![WindowRuleCondition {
|
||||
class: Some(vec!["steam".to_string()]),
|
||||
tag: Some(vec![TagId(0), TagId(1)]),
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
WindowRule {
|
||||
fullscreen_or_maximized: Some(FullscreenOrMaximized::Fullscreen),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: window_begin_move
|
||||
// TODO: window_begin_resize
|
||||
|
||||
mod handle {
|
||||
use super::*;
|
||||
|
||||
// WindowHandle
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn spawn() -> anyhow::Result<()> {
|
||||
async fn close() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.spawn("foot")
|
||||
|
@ -128,424 +288,374 @@ mod coverage {
|
|||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.windows.len(), 1);
|
||||
assert_eq!(state.windows[0].class(), Some("foot".to_string()));
|
||||
});
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:close()
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.windows.len(), 0);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn set_env() -> anyhow::Result<()> {
|
||||
async fn move_to_tag() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.set_env("PROCESS_SET_ENV", "env value")
|
||||
local tags = Pinnacle.tag.add(Pinnacle.output.get_focused(), "1", "2", "3")
|
||||
tags[1]:set_active(true)
|
||||
tags[2]:set_active(true)
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |_state| {
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
std::env::var("PROCESS_SET_ENV"),
|
||||
Ok("env value".to_string())
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["1", "2"]
|
||||
);
|
||||
});
|
||||
|
||||
// Correct usage
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:move_to_tag(Pinnacle.tag.get("3"))
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["3"]
|
||||
);
|
||||
});
|
||||
|
||||
// Move to the same tag
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:move_to_tag(Pinnacle.tag.get("3"))
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["3"]
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Window
|
||||
mod tag {
|
||||
use super::*;
|
||||
|
||||
mod window {
|
||||
mod handle {
|
||||
use super::*;
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn get_all() -> anyhow::Result<()> {
|
||||
async fn props() -> anyhow::Result<()> {
|
||||
test_api(|_sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
assert(#Pinnacle.window.get_all() == 0)
|
||||
|
||||
for i = 1, 5 do
|
||||
Pinnacle.process.spawn("foot")
|
||||
end
|
||||
Pinnacle.output.connect_for_all(function(op)
|
||||
local tags = Pinnacle.tag.add(op, "First", "Mungus", "Potato")
|
||||
tags[1]:set_active(true)
|
||||
tags[3]:set_active(true)
|
||||
end)
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
assert(#Pinnacle.window.get_all() == 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn get_focused() -> anyhow::Result<()> {
|
||||
test_api(|_sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
assert(not Pinnacle.window.get_focused())
|
||||
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "1")[1]:set_active(true)
|
||||
Pinnacle.process.spawn("foot")
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
assert(Pinnacle.window.get_focused())
|
||||
local first_props = Pinnacle.tag.get("First"):props()
|
||||
assert(first_props.active == true)
|
||||
assert(first_props.name == "First")
|
||||
assert(first_props.output.name == "Pinnacle Window")
|
||||
assert(#first_props.windows == 2)
|
||||
assert(first_props.windows[1]:class() == "foot")
|
||||
assert(first_props.windows[2]:class() == "foot")
|
||||
|
||||
local mungus_props = Pinnacle.tag.get("Mungus"):props()
|
||||
assert(mungus_props.active == false)
|
||||
assert(mungus_props.name == "Mungus")
|
||||
assert(mungus_props.output.name == "Pinnacle Window")
|
||||
assert(#mungus_props.windows == 0)
|
||||
|
||||
local potato_props = Pinnacle.tag.get("Potato"):props()
|
||||
assert(potato_props.active == true)
|
||||
assert(potato_props.name == "Potato")
|
||||
assert(potato_props.output.name == "Pinnacle Window")
|
||||
assert(#potato_props.windows == 2)
|
||||
assert(potato_props.windows[1]:class() == "foot")
|
||||
assert(potato_props.windows[2]:class() == "foot")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn add_window_rule() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "Tag Name")
|
||||
Pinnacle.window.add_window_rule({
|
||||
cond = { classes = { "firefox" } },
|
||||
rule = { tags = { Pinnacle.tag.get("Tag Name") } },
|
||||
})
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.config.window_rules.len(), 1);
|
||||
assert_eq!(
|
||||
state.config.window_rules[0],
|
||||
(
|
||||
WindowRuleCondition {
|
||||
class: Some(vec!["firefox".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
WindowRule {
|
||||
tags: Some(vec![TagId(0)]),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.tag.add(Pinnacle.output.get_focused(), "Tag Name 2")
|
||||
Pinnacle.window.add_window_rule({
|
||||
cond = {
|
||||
all = {
|
||||
{
|
||||
classes = { "steam" },
|
||||
tags = {
|
||||
Pinnacle.tag.get("Tag Name"),
|
||||
Pinnacle.tag.get("Tag Name 2"),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
rule = { fullscreen_or_maximized = "fullscreen" },
|
||||
})
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.config.window_rules.len(), 2);
|
||||
assert_eq!(
|
||||
state.config.window_rules[1],
|
||||
(
|
||||
WindowRuleCondition {
|
||||
cond_all: Some(vec![WindowRuleCondition {
|
||||
class: Some(vec!["steam".to_string()]),
|
||||
tag: Some(vec![TagId(0), TagId(1)]),
|
||||
..Default::default()
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
WindowRule {
|
||||
fullscreen_or_maximized: Some(FullscreenOrMaximized::Fullscreen),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: window_begin_move
|
||||
// TODO: window_begin_resize
|
||||
|
||||
mod handle {
|
||||
use super::*;
|
||||
|
||||
// WindowHandle
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn close() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.windows.len(), 1);
|
||||
});
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:close()
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(state.windows.len(), 0);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn move_to_tag() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
local tags = Pinnacle.tag.add(Pinnacle.output.get_focused(), "1", "2", "3")
|
||||
tags[1]:set_active(true)
|
||||
tags[2]:set_active(true)
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["1", "2"]
|
||||
);
|
||||
});
|
||||
|
||||
// Correct usage
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:move_to_tag(Pinnacle.tag.get("3"))
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["3"]
|
||||
);
|
||||
});
|
||||
|
||||
// Move to the same tag
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.window.get_all()[1]:move_to_tag(Pinnacle.tag.get("3"))
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
assert_eq!(
|
||||
state.windows[0].with_state(|st| st
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["3"]
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tag {
|
||||
use super::*;
|
||||
mod output {
|
||||
use smithay::{output::Output, utils::Rectangle};
|
||||
|
||||
mod handle {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn props() -> anyhow::Result<()> {
|
||||
test_api(|_sender| {
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.output.connect_for_all(function(op)
|
||||
local tags = Pinnacle.tag.add(op, "First", "Mungus", "Potato")
|
||||
tags[1]:set_active(true)
|
||||
tags[3]:set_active(true)
|
||||
end)
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
Pinnacle.process.spawn("foot")
|
||||
Pinnacle.process.spawn("foot")
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
run_lua! { |Pinnacle|
|
||||
local first_props = Pinnacle.tag.get("First"):props()
|
||||
assert(first_props.active == true)
|
||||
assert(first_props.name == "First")
|
||||
assert(first_props.output.name == "Pinnacle Window")
|
||||
assert(#first_props.windows == 2)
|
||||
assert(first_props.windows[1]:class() == "foot")
|
||||
assert(first_props.windows[2]:class() == "foot")
|
||||
|
||||
local mungus_props = Pinnacle.tag.get("Mungus"):props()
|
||||
assert(mungus_props.active == false)
|
||||
assert(mungus_props.name == "Mungus")
|
||||
assert(mungus_props.output.name == "Pinnacle Window")
|
||||
assert(#mungus_props.windows == 0)
|
||||
|
||||
local potato_props = Pinnacle.tag.get("Potato"):props()
|
||||
assert(potato_props.active == true)
|
||||
assert(potato_props.name == "Potato")
|
||||
assert(potato_props.output.name == "Pinnacle Window")
|
||||
assert(#potato_props.windows == 2)
|
||||
assert(first_props.windows[1]:class() == "foot")
|
||||
assert(first_props.windows[2]:class() == "foot")
|
||||
}
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
setup_lua! { |Pinnacle|
|
||||
Pinnacle.output.setup({
|
||||
{
|
||||
function(_)
|
||||
return true
|
||||
end,
|
||||
tag_names = { "1", "2", "3" },
|
||||
},
|
||||
{
|
||||
function(op)
|
||||
return string.match(op.name, "Test") ~= nil
|
||||
end,
|
||||
tag_names = { "Test 4", "Test 5" },
|
||||
},
|
||||
{
|
||||
"Second",
|
||||
scale = 2.0,
|
||||
mode = {
|
||||
pixel_width = 6900,
|
||||
pixel_height = 420,
|
||||
refresh_rate_millihz = 69420,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
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);
|
||||
|
||||
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 test_third_op = output_for_name(state, "Test Third");
|
||||
|
||||
let tags_for = |output: &Output| {
|
||||
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!(
|
||||
tags_for(&test_third_op),
|
||||
vec!["1", "2", "3", "Test 4", "Test 5"]
|
||||
);
|
||||
|
||||
assert_eq!(focused_tags_for(&original_op), vec!["1"]);
|
||||
assert_eq!(focused_tags_for(&test_third_op), vec!["1"]);
|
||||
|
||||
assert_eq!(second_op.current_scale().fractional_scale(), 2.0);
|
||||
|
||||
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);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
mod output {
|
||||
use smithay::utils::Rectangle;
|
||||
#[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" } },
|
||||
})
|
||||
}
|
||||
|
||||
use super::*;
|
||||
sleep_secs(1);
|
||||
|
||||
#[tokio::main]
|
||||
#[self::test]
|
||||
async fn setup() -> anyhow::Result<()> {
|
||||
test_api(|sender| {
|
||||
setup_lua! { |Pinnacle|
|
||||
Pinnacle.output.setup({
|
||||
{
|
||||
function(_)
|
||||
return true
|
||||
end,
|
||||
tag_names = { "First", "Third", "Schmurd" },
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("First", (300, 200).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 original_geo = state.space.output_geometry(&original_op).unwrap();
|
||||
let first_geo = state.space.output_geometry(&first_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
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("Second", (500, 500).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 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();
|
||||
|
||||
assert_eq!(
|
||||
original_geo,
|
||||
Rectangle::from_loc_and_size((0, 0), (1920, 1080))
|
||||
);
|
||||
assert_eq!(
|
||||
first_geo,
|
||||
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" },
|
||||
},
|
||||
{
|
||||
"Pinnacle Window",
|
||||
loc = { x = 300, y = 0 },
|
||||
},
|
||||
{
|
||||
"Output 1",
|
||||
loc = { "Pinnacle Window", "bottom_align_left" },
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
sleep_secs(1);
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
state.new_output("Output 1", (960, 540).into());
|
||||
});
|
||||
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);
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let original_op = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Pinnacle Window")
|
||||
.unwrap();
|
||||
let output_1 = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Output 1")
|
||||
.unwrap();
|
||||
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_op_geo = state.space.output_geometry(original_op).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();
|
||||
let second_geo = state.space.output_geometry(&second_op).unwrap();
|
||||
let third_geo = state.space.output_geometry(&third_op).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
original_op_geo,
|
||||
Rectangle::from_loc_and_size((300, 0), (1920, 1080))
|
||||
);
|
||||
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))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output_1_geo,
|
||||
Rectangle::from_loc_and_size((300, 1080), (960, 540))
|
||||
);
|
||||
state.remove_output(&second_op);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
output_1.with_state(|state| state
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()),
|
||||
vec!["First", "Third", "Schmurd"]
|
||||
);
|
||||
sleep_secs(1);
|
||||
|
||||
state.remove_output(&original_op.clone());
|
||||
});
|
||||
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");
|
||||
|
||||
sleep_secs(1);
|
||||
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();
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let output_1 = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Output 1")
|
||||
.unwrap();
|
||||
|
||||
let output_1_geo = state.space.output_geometry(output_1).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output_1_geo,
|
||||
Rectangle::from_loc_and_size((0, 0), (960, 540))
|
||||
);
|
||||
|
||||
state.new_output("Output 2", (300, 500).into());
|
||||
});
|
||||
|
||||
sleep_secs(1);
|
||||
|
||||
with_state(&sender, |state| {
|
||||
let output_1 = state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == "Output 1")
|
||||
.unwrap();
|
||||
|
||||
let output_2 = state
|
||||
.space
|
||||
.outputs()
|
||||
.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!(
|
||||
output_2_geo,
|
||||
Rectangle::from_loc_and_size((0, 0), (300, 500))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output_1_geo,
|
||||
Rectangle::from_loc_and_size((300, 0), (960, 540))
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
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]
|
||||
#[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| {
|
||||
setup_rust(|api| {
|
||||
api.output.setup_locs(
|
||||
|
|
Loading…
Reference in a new issue