monitor: refactor inheritance, expand query, add unit tests for position and query (#15073)

This commit is contained in:
Vaxry
2026-06-11 17:24:15 +01:00
committed by GitHub
parent fed8884d21
commit 4d7ae07549
17 changed files with 1193 additions and 325 deletions
+254
View File
@@ -0,0 +1,254 @@
#include <state/MonitorPositionController.hpp>
#include <config/shared/monitor/MonitorRule.hpp>
#include <gtest/gtest.h>
#include <optional>
#include <string>
#include <vector>
class CPositionTestMonitor : public Monitor::IMonitorArrangeable {
public:
CPositionTestMonitor(MONITORID id, const std::string& name, const Vector2D& size) : m_id(id), m_name(name), m_size(size), m_transformedSize(size) {
;
}
virtual MONITORID id() const override {
return m_id;
}
virtual std::string_view name() const override {
return m_name;
}
virtual std::string_view description() const override {
return m_description;
}
virtual std::string_view shortDescription() const override {
return m_shortDescription;
}
virtual bool matchesStaticSelector(std::string_view selector) const override {
return selector == m_name;
}
virtual Vector2D position() const override {
return m_position;
}
virtual Vector2D size() const override {
return m_size;
}
virtual Vector2D pixelSize() const override {
return m_size;
}
virtual Vector2D transformedSize() const override {
return m_transformedSize;
}
virtual float scale() const override {
return m_scale;
}
virtual Hyprutils::Math::eTransform transform() const override {
return m_transform;
}
virtual CBox logicalBox() const override {
return {m_position, m_size};
}
virtual CBox logicalBoxMinusReserved() const override {
return logicalBox();
}
virtual Vector2D middle() const override {
return m_position + m_size / 2.F;
}
virtual void moveTo(const Vector2D& pos) override {
m_position = pos;
}
virtual std::optional<Vector2D> explicitPosition() const override {
return m_explicitPosition;
}
virtual Config::eAutoDirs autoDirection() const override {
return m_autoDirection;
}
virtual Vector2D xwaylandPosition() const override {
return m_xwaylandPosition;
}
virtual float xwaylandScale() const override {
return m_xwaylandScale;
}
virtual void setXWaylandPosition(const Vector2D& pos) override {
m_xwaylandPosition = pos;
}
virtual void setXWaylandScale(float scale) override {
m_xwaylandScale = scale;
}
MONITORID m_id = 0;
std::string m_name;
std::string m_description;
std::string m_shortDescription;
Vector2D m_position;
Vector2D m_size;
Vector2D m_transformedSize;
float m_scale = 1.F;
Hyprutils::Math::eTransform m_transform = Hyprutils::Math::HYPRUTILS_TRANSFORM_NORMAL;
std::optional<Vector2D> m_explicitPosition = {};
Config::eAutoDirs m_autoDirection = Config::DIR_AUTO_RIGHT;
Vector2D m_xwaylandPosition;
float m_xwaylandScale = 1.F;
};
static SP<CPositionTestMonitor> testMonitor(MONITORID id, const std::string& name, const Vector2D& size) {
return makeShared<CPositionTestMonitor>(id, name, size);
}
static SP<Monitor::IMonitorArrangeable> arrangeable(const SP<CPositionTestMonitor>& monitor) {
return dynamicPointerCast<Monitor::IMonitorArrangeable>(monitor);
}
static void arrange(const std::vector<SP<CPositionTestMonitor>>& monitors, bool xwaylandForceZeroScaling = false) {
std::vector<SP<Monitor::IMonitorArrangeable>> arrangeableMonitors;
arrangeableMonitors.reserve(monitors.size());
for (const auto& monitor : monitors)
arrangeableMonitors.push_back(arrangeable(monitor));
State::CMonitorPositionController{}.arrange(arrangeableMonitors, xwaylandForceZeroScaling);
}
TEST(MonitorPositionController, explicitPositionsAreAppliedFirst) {
const auto left = testMonitor(0, "left", {100, 100});
const auto right = testMonitor(1, "right", {100, 100});
const auto autoM = testMonitor(2, "auto", {50, 50});
left->m_explicitPosition = Vector2D{-100, 0};
right->m_explicitPosition = Vector2D{100, 0};
autoM->m_autoDirection = Config::DIR_AUTO_RIGHT;
arrange({left, autoM, right});
EXPECT_EQ(left->m_position, Vector2D(-100, 0));
EXPECT_EQ(right->m_position, Vector2D(100, 0));
EXPECT_EQ(autoM->m_position, Vector2D(200, 0));
}
TEST(MonitorPositionController, autoRightPlacesAfterRightmostEdge) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {50, 50});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_RIGHT;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(100, 0));
}
TEST(MonitorPositionController, autoLeftPlacesBeforeLeftmostEdge) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {50, 50});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_LEFT;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(-50, 0));
}
TEST(MonitorPositionController, autoDownPlacesBelowLowestEdge) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {50, 50});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_DOWN;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(0, 100));
}
TEST(MonitorPositionController, autoUpPlacesAboveHighestEdge) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {50, 50});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_UP;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(0, -50));
}
TEST(MonitorPositionController, autoCenterRightCentersVertically) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {50, 40});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_CENTER_RIGHT;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(100, 30));
}
TEST(MonitorPositionController, autoCenterDownCentersHorizontally) {
const auto explicitM = testMonitor(0, "explicit", {100, 100});
const auto autoM = testMonitor(1, "auto", {40, 50});
explicitM->m_explicitPosition = Vector2D{0, 0};
autoM->m_autoDirection = Config::DIR_AUTO_CENTER_DOWN;
arrange({explicitM, autoM});
EXPECT_EQ(autoM->m_position, Vector2D(30, 100));
}
TEST(MonitorPositionController, xwaylandPositionsUseLogicalSizeByDefault) {
const auto first = testMonitor(0, "first", {100, 100});
const auto second = testMonitor(1, "second", {50, 50});
first->m_explicitPosition = Vector2D{0, 0};
second->m_explicitPosition = Vector2D{100, 0};
first->m_transformedSize = Vector2D{200, 100};
first->m_scale = 2.F;
arrange({first, second}, false);
EXPECT_EQ(first->m_xwaylandPosition, Vector2D(0, 0));
EXPECT_EQ(second->m_xwaylandPosition, Vector2D(100, 0));
EXPECT_FLOAT_EQ(first->m_xwaylandScale, 1.F);
EXPECT_FLOAT_EQ(second->m_xwaylandScale, 1.F);
}
TEST(MonitorPositionController, xwaylandPositionsUseTransformedSizeWhenForceZeroScaling) {
const auto first = testMonitor(0, "first", {100, 100});
const auto second = testMonitor(1, "second", {50, 50});
first->m_explicitPosition = Vector2D{0, 0};
second->m_explicitPosition = Vector2D{100, 0};
first->m_transformedSize = Vector2D{200, 100};
first->m_scale = 2.F;
arrange({first, second}, true);
EXPECT_EQ(first->m_xwaylandPosition, Vector2D(0, 0));
EXPECT_EQ(second->m_xwaylandPosition, Vector2D(200, 0));
EXPECT_FLOAT_EQ(first->m_xwaylandScale, 2.F);
EXPECT_FLOAT_EQ(second->m_xwaylandScale, 1.F);
}
+247
View File
@@ -0,0 +1,247 @@
#include <state/MonitorQueryCore.hpp>
#include <aquamarine/output/Output.hpp>
#include <gtest/gtest.h>
#include <hyprutils/string/String.hpp>
#include <optional>
#include <string>
#include <utility>
#include <vector>
using namespace Hyprutils::String;
class CQueryTestMonitor : public Monitor::IMonitorQueryable {
public:
CQueryTestMonitor(MONITORID id, const std::string& name, const Vector2D& position, const Vector2D& size) : m_id(id), m_name(name), m_position(position), m_size(size) {
;
}
virtual MONITORID id() const override {
return m_id;
}
virtual std::string_view name() const override {
return m_name;
}
virtual std::string_view description() const override {
return m_description;
}
virtual std::string_view shortDescription() const override {
return m_shortDescription;
}
virtual bool matchesStaticSelector(std::string_view selector) const override {
if (m_selectorResult.has_value())
return *m_selectorResult;
if (selector.starts_with("desc:")) {
const auto DESCRIPTIONSELECTOR = trim(selector.substr(5));
return m_description.starts_with(DESCRIPTIONSELECTOR) || m_shortDescription.starts_with(DESCRIPTIONSELECTOR);
}
return selector == m_name;
}
virtual Vector2D position() const override {
return m_position;
}
virtual Vector2D size() const override {
return m_size;
}
virtual Vector2D pixelSize() const override {
return m_size;
}
virtual Vector2D transformedSize() const override {
return m_size;
}
virtual float scale() const override {
return 1.F;
}
virtual Hyprutils::Math::eTransform transform() const override {
return Hyprutils::Math::HYPRUTILS_TRANSFORM_NORMAL;
}
virtual CBox logicalBox() const override {
return {m_position, m_size};
}
virtual CBox logicalBoxMinusReserved() const override {
return logicalBox();
}
virtual Vector2D middle() const override {
return m_position + m_size / 2.F;
}
virtual bool enabled() const override {
return m_enabled;
}
virtual bool hasOutput() const override {
return m_hasOutput;
}
virtual SP<Aquamarine::IOutput> output() const override {
return m_output;
}
MONITORID m_id = 0;
std::string m_name;
std::string m_description;
std::string m_shortDescription;
Vector2D m_position;
Vector2D m_size;
bool m_enabled = true;
bool m_hasOutput = true;
SP<Aquamarine::IOutput> m_output;
std::optional<bool> m_selectorResult;
};
static SP<CQueryTestMonitor> testMonitor(MONITORID id, const std::string& name, const Vector2D& position, const Vector2D& size = {100, 100}) {
return makeShared<CQueryTestMonitor>(id, name, position, size);
}
static SP<Monitor::IMonitorQueryable> queryable(const SP<CQueryTestMonitor>& monitor) {
return dynamicPointerCast<Monitor::IMonitorQueryable>(monitor);
}
static std::vector<SP<Monitor::IMonitorQueryable>> queryables(const std::vector<SP<CQueryTestMonitor>>& monitors) {
std::vector<SP<Monitor::IMonitorQueryable>> result;
result.reserve(monitors.size());
for (const auto& monitor : monitors)
result.push_back(queryable(monitor));
return result;
}
TEST(MonitorQueryCore, queryById) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).id(1).run(), queryable(second));
}
TEST(MonitorQueryCore, queryByName) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "HDMI-A-1", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).name("HDMI-A-1").run(), queryable(second));
}
TEST(MonitorQueryCore, queryByDescriptionPrefix) {
const auto first = testMonitor(0, "DP-1", {0, 0});
auto monitors = queryables({first});
first->m_description = "Dell Inc. ABC";
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).description("Dell").run(), queryable(first));
EXPECT_FALSE(std::move(State::CMonitorQueryCore{monitors}).description("Nope").run());
}
TEST(MonitorQueryCore, queryByVectorInsideBox) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).vec({150, 50}).run(), queryable(second));
}
TEST(MonitorQueryCore, closestMonitorForVectorOutsideAllBoxes) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {300, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).vec({250, 50}).run(), queryable(second));
}
TEST(MonitorQueryCore, directionLookupRight) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(first)).inDirection(Math::DIRECTION_RIGHT).run(), queryable(second));
}
TEST(MonitorQueryCore, directionLookupChoosesLongestIntersection) {
const auto reference = testMonitor(0, "reference", {0, 0}, {100, 100});
const auto small = testMonitor(1, "small", {100, 80}, {100, 20});
const auto large = testMonitor(2, "large", {100, 0}, {100, 60});
auto monitors = queryables({reference, small, large});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(reference)).inDirection(Math::DIRECTION_RIGHT).run(), queryable(large));
}
TEST(MonitorQueryCore, configCurrentReturnsRelativeMonitor) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(second)).configString("current").run(), queryable(second));
}
TEST(MonitorQueryCore, configDirectionUsesRelativeMonitor) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(first)).configString("r").run(), queryable(second));
}
TEST(MonitorQueryCore, configRelativePositiveWraps) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
const auto third = testMonitor(2, "DP-3", {200, 0});
auto monitors = queryables({first, second, third});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(second)).configString("+1").run(), queryable(third));
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(third)).configString("+1").run(), queryable(first));
}
TEST(MonitorQueryCore, configRelativeNegativeWraps) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
const auto third = testMonitor(2, "DP-3", {200, 0});
auto monitors = queryables({first, second, third});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).relativeTo(queryable(first)).configString("-1").run(), queryable(third));
}
TEST(MonitorQueryCore, configNumericId) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).configString("1").run(), queryable(second));
}
TEST(MonitorQueryCore, configSelectorSkipsNoOutput) {
const auto withoutOutput = testMonitor(0, "DP-1", {0, 0});
const auto withOutput = testMonitor(1, "DP-1", {100, 0});
auto monitors = queryables({withoutOutput, withOutput});
withoutOutput->m_hasOutput = false;
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).configString("DP-1").run(), queryable(withOutput));
}
TEST(MonitorQueryCore, selectorQueryUsesInterfaceMatcher) {
const auto first = testMonitor(0, "DP-1", {0, 0});
const auto second = testMonitor(1, "DP-2", {100, 0});
auto monitors = queryables({first, second});
first->m_selectorResult = false;
second->m_selectorResult = true;
EXPECT_EQ(std::move(State::CMonitorQueryCore{monitors}).selector("anything").run(), queryable(second));
}