Compare commits

..

No commits in common. "main" and "v0.54.0" have entirely different histories.

176 changed files with 3163 additions and 6240 deletions

View file

@ -125,7 +125,6 @@ find_package(Threads REQUIRED)
set(GLES_VERSION "GLES3") set(GLES_VERSION "GLES3")
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
find_package(glslang CONFIG REQUIRED)
set(AQUAMARINE_MINIMUM_VERSION 0.9.3) set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
set(HYPRLANG_MINIMUM_VERSION 0.6.7) set(HYPRLANG_MINIMUM_VERSION 0.6.7)
@ -267,8 +266,7 @@ pkg_check_modules(
gbm gbm
gio-2.0 gio-2.0
re2 re2
muparser muparser)
lcms2)
find_package(hyprwayland-scanner 0.3.10 REQUIRED) find_package(hyprwayland-scanner 0.3.10 REQUIRED)
@ -480,9 +478,9 @@ function(protocolWayland)
endfunction() endfunction()
if(TARGET OpenGL::GL) if(TARGET OpenGL::GL)
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
else() else()
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 Threads::Threads)
endif() endif()
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4) pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)

View file

@ -18,7 +18,6 @@ nopch:
clear: clear:
rm -rf build rm -rf build
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
all: all:
$(MAKE) clear $(MAKE) clear

18
flake.lock generated
View file

@ -16,11 +16,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772292445, "lastModified": 1771610171,
"narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=", "narHash": "sha256-+DeInuhbm6a6PpHDNUS7pozDouq2+8xSDefoNaZLW0E=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "aquamarine", "repo": "aquamarine",
"rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f", "rev": "7f9eb087703ec4acc6b288d02fa9ea3db803cd3d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -325,11 +325,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1772198003, "lastModified": 1771848320,
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "rev": "2fc6539b481e1d2569f25f8799236694180c0993",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -348,11 +348,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772024342, "lastModified": 1771858127,
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=", "narHash": "sha256-Gtre9YoYl3n25tJH2AoSdjuwcqij5CPxL3U3xysYD08=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476", "rev": "49bbbfc218bf3856dfa631cead3b052d78248b83",
"type": "github" "type": "github"
}, },
"original": { "original": {

206
flake.nix
View file

@ -88,122 +88,108 @@
}; };
}; };
outputs = outputs = inputs @ {
inputs@{ self,
self, nixpkgs,
nixpkgs, systems,
systems, ...
... }: let
}: inherit (nixpkgs) lib;
let eachSystem = lib.genAttrs (import systems);
inherit (nixpkgs) lib; pkgsFor = eachSystem (system:
eachSystem = lib.genAttrs (import systems); import nixpkgs {
pkgsFor = eachSystem ( localSystem = system;
system: overlays = with self.overlays; [
import nixpkgs { hyprland-packages
localSystem = system; hyprland-extras
overlays = with self.overlays; [ ];
hyprland-packages });
hyprland-extras pkgsCrossFor = eachSystem (system: crossSystem:
]; import nixpkgs {
} localSystem = system;
); inherit crossSystem;
pkgsCrossFor = eachSystem ( overlays = with self.overlays; [
system: crossSystem: hyprland-packages
import nixpkgs { hyprland-extras
localSystem = system; ];
inherit crossSystem; });
overlays = with self.overlays; [ pkgsDebugFor = eachSystem (system:
hyprland-packages import nixpkgs {
hyprland-extras localSystem = system;
]; overlays = with self.overlays; [
} hyprland-debug
); ];
pkgsDebugFor = eachSystem ( });
system: pkgsDebugCrossFor = eachSystem (system: crossSystem:
import nixpkgs { import nixpkgs {
localSystem = system; localSystem = system;
overlays = with self.overlays; [ inherit crossSystem;
hyprland-debug overlays = with self.overlays; [
]; hyprland-debug
} ];
); });
pkgsDebugCrossFor = eachSystem ( in {
system: crossSystem: overlays = import ./nix/overlays.nix {inherit self lib inputs;};
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-debug
];
}
);
in
{
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
checks = eachSystem ( checks = eachSystem (system:
system: (lib.filterAttrs
(lib.filterAttrs ( (n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n) self.packages.${system})
) self.packages.${system}) // {
// { inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
inherit (self.packages.${system}) xdg-desktop-portal-hyprland; pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { src = ./.;
src = ./.; hooks = {
hooks = { hyprland-treewide-formatter = {
hyprland-treewide-formatter = { enable = true;
enable = true; entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; pass_filenames = false;
pass_filenames = false; excludes = ["subprojects"];
excludes = [ "subprojects" ]; always_run = true;
always_run = true;
};
}; };
}; };
} };
// (import ./nix/tests inputs pkgsFor.${system}) }
); // (import ./nix/tests inputs pkgsFor.${system}));
packages = eachSystem (system: { packages = eachSystem (system: {
default = self.packages.${system}.hyprland; default = self.packages.${system}.hyprland;
inherit (pkgsFor.${system}) inherit
# hyprland-packages (pkgsFor.${system})
hyprland # hyprland-packages
hyprland-unwrapped hyprland
hyprland-with-tests hyprland-unwrapped
# hyprland-extras hyprland-with-tests
xdg-desktop-portal-hyprland # hyprland-extras
; xdg-desktop-portal-hyprland
inherit (pkgsDebugFor.${system}) hyprland-debug; ;
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland; inherit (pkgsDebugFor.${system}) hyprland-debug;
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug; hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
}); hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
});
devShells = eachSystem (system: { devShells = eachSystem (system: {
default = default =
pkgsFor.${system}.mkShell.override pkgsFor.${system}.mkShell.override {
{ inherit (self.packages.${system}.default) stdenv;
inherit (self.packages.${system}.default) stdenv; } {
} name = "hyprland-shell";
{ hardeningDisable = ["fortify"];
name = "hyprland-shell"; inputsFrom = [pkgsFor.${system}.hyprland];
hardeningDisable = [ "fortify" ]; packages = [pkgsFor.${system}.clang-tools];
inputsFrom = [ pkgsFor.${system}.hyprland ]; inherit (self.checks.${system}.pre-commit-check) shellHook;
packages = [ pkgsFor.${system}.clang-tools ]; };
inherit (self.checks.${system}.pre-commit-check) shellHook; });
};
});
formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix { }); formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix {});
nixosModules.default = import ./nix/module.nix inputs; nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self; homeManagerModules.default = import ./nix/hm-module.nix self;
# Hydra build jobs # Hydra build jobs
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix # Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can # or similar. Remember to filter large or incompatible attributes here. More eval jobs can
# be added by merging, e.g., self.packages // self.devShells. # be added by merging, e.g., self.packages // self.devShells.
hydraJobs = self.packages; hydraJobs = self.packages;
}; };
} }

View file

@ -228,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
constexpr size_t BUFFER_SIZE = 8192; constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0}; char buffer[BUFFER_SIZE] = {0};
// read all data until server closes the connection sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
// this handles partial writes on the server side under high load
while (true) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (6)");
return 6;
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) { if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (6)"); log("Couldn't read (6)");
return 6; return 6;
} }
if (sizeWritten == 0) {
// server closed connection, we're done
break;
}
reply += std::string(buffer, sizeWritten); reply += std::string(buffer, sizeWritten);
} }

View file

@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
find_package(glaze 6.0.1 QUIET) find_package(glaze 7.0.0 QUIET)
if (NOT glaze_FOUND) if (NOT glaze_FOUND)
set(GLAZE_VERSION v6.0.1) set(GLAZE_VERSION v7.0.0)
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(

View file

@ -131,18 +131,9 @@ bool CPluginManager::createSafeDirectory(const std::string& path) {
return true; return true;
} }
bool CPluginManager::validArg(const std::string& s) {
return !s.contains("'") && !s.ends_with("\\") && !s.starts_with("\\");
}
bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) { bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) {
const auto HLVER = getHyprlandVersion(); const auto HLVER = getHyprlandVersion();
if (!validArg(url) || !validArg(rev)) {
std::println(stderr, "\n{}", failureString("url or rev invalid"));
return false;
}
if (!hasDeps()) { if (!hasDeps()) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc"));
return false; return false;
@ -207,7 +198,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Cloning {}", url)); progress.printMessageAbove(infoString("Cloning {}", url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME)); std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME));
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
@ -512,11 +503,11 @@ bool CPluginManager::updateHeaders(bool force) {
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
std::string ret = std::string ret =
execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
if (!std::filesystem::exists(WORKINGDIR)) { if (!std::filesystem::exists(WORKINGDIR)) {
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME)); ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME));
} }
if (!std::filesystem::exists(WORKINGDIR + "/.git")) { if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
@ -657,7 +648,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion(false);
CProgressBar progress; CProgressBar progress;
progress.m_iMaxSteps = (REPOS.size() * 2) + 2; progress.m_iMaxSteps = REPOS.size() * 2 + 2;
progress.m_iSteps = 0; progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Updating repositories"; progress.m_szCurrentMessage = "Updating repositories";
progress.print(); progress.print();
@ -678,7 +669,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Cloning {}", repo.url)); progress.printMessageAbove(infoString("Cloning {}", repo.url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME)); std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME));
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
@ -688,7 +679,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (!repo.rev.empty()) { if (!repo.rev.empty()) {
progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev)); progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev));
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'"); std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev);
if (ret.compare(0, 6, "fatal:") == 0) { if (ret.compare(0, 6, "fatal:") == 0) {
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret)); std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));

View file

@ -81,7 +81,6 @@ class CPluginManager {
private: private:
std::string headerError(const eHeadersErrors err); std::string headerError(const eHeadersErrors err);
std::string headerErrorShort(const eHeadersErrors err); std::string headerErrorShort(const eHeadersErrors err);
bool validArg(const std::string& s);
std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver); std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver);

View file

@ -10,9 +10,7 @@
#include <src/managers/PointerManager.hpp> #include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp> #include <src/managers/input/trackpad/TrackpadGestures.hpp>
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp> #include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>
#include <src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp>
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp> #include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
#include <src/desktop/view/LayerSurface.hpp>
#include <src/Compositor.hpp> #include <src/Compositor.hpp>
#include <src/desktop/state/FocusState.hpp> #include <src/desktop/state/FocusState.hpp>
#include <src/layout/LayoutManager.hpp> #include <src/layout/LayoutManager.hpp>
@ -272,67 +270,32 @@ static SDispatchResult keybind(std::string in) {
return {}; return {};
} }
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
// //
static SDispatchResult addWindowRule(std::string in) { static SDispatchResult addRule(std::string in) {
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
return {.success = false, .error = "re-registering returned a different id?"}; return {.success = false, .error = "re-registering returned a different id?"};
return {}; return {};
} }
static SDispatchResult checkWindowRule(std::string in) { static SDispatchResult checkRule(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW) if (!PLASTWINDOW)
return {.success = false, .error = "No window"}; return {.success = false, .error = "No window"};
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX)) if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
return {.success = false, .error = "No rule"}; return {.success = false, .error = "No rule"};
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect") if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
return {.success = false, .error = "Effect isn't \"effect\""}; return {.success = false, .error = "Effect isn't \"effect\""};
return {}; return {};
} }
static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0;
static SDispatchResult addLayerRule(std::string in) {
layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule");
if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX)
return {.success = false, .error = "re-registering returned a different id?"};
return {};
}
static SDispatchResult checkLayerRule(std::string in) {
if (g_pCompositor->m_layers.size() != 3)
return {.success = false, .error = "Layers under test not here"};
for (const auto& layer : g_pCompositor->m_layers) {
if (layer->m_namespace == "rule-layer") {
if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
return {.success = false, .error = "No rule"};
if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect")
return {.success = false, .error = "Effect isn't \"effect\""};
} else if (layer->m_namespace == "norule-layer") {
if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
return {.success = false, .error = "Rule even though it shouldn't"};
} else
return {.success = false, .error = "Unrecognized layer"};
}
return {};
}
static SDispatchResult floatingFocusOnFullscreen(std::string in) { static SDispatchResult floatingFocusOnFullscreen(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PLASTWINDOW = Desktop::focusState()->window();
@ -362,10 +325,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen);
// init mouse // init mouse

View file

@ -39,16 +39,6 @@ namespace Colors {
TESTS_PASSED++; \ TESTS_PASSED++; \
} }
#define EXPECT_NOT(expr, val) \
if (const auto RESULT = expr; RESULT == (val)) { \
NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
TESTS_PASSED++; \
}
#define EXPECT_VECTOR2D(expr, val) \ #define EXPECT_VECTOR2D(expr, val) \
do { \ do { \
const auto& RESULT = expr; \ const auto& RESULT = expr; \

View file

@ -118,33 +118,6 @@ static bool test() {
Tests::killAllWindows(); Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);
// test that child windows (shouldBeFloated) are not auto-grouped
NLog::log("{}Test child windows are not auto-grouped", Colors::GREEN);
auto kitty = Tests::spawnKitty();
if (!kitty) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
// create group and enable auto-grouping
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:auto_group true"));
SClient client2;
if (!startClient(client2))
return false;
EXPECT(Tests::windowCount(), 2);
createChild(client2);
EXPECT(Tests::windowCount(), 3);
// child has set_parent so shouldBeFloated returns true, it should not be auto-grouped
EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1);
stopClient(client2);
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
return !ret; return !ret;
} }

View file

@ -64,16 +64,16 @@ static void test13349() {
{ {
auto str = getFromSocket("/activewindow"); auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 497,22"); EXPECT_CONTAINS(str, "at: 22,547");
EXPECT_CONTAINS(str, "size: 456,1036"); EXPECT_CONTAINS(str, "size: 931,511");
} }
OK(getFromSocket("/dispatch movewindow r")); OK(getFromSocket("/dispatch movewindow r"));
{ {
auto str = getFromSocket("/activewindow"); auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 967,22"); EXPECT_CONTAINS(str, "at: 967,547");
EXPECT_CONTAINS(str, "size: 456,1036"); EXPECT_CONTAINS(str, "size: 931,511");
} }
// clean up // clean up
@ -81,152 +81,6 @@ static void test13349() {
Tests::killAllWindows(); Tests::killAllWindows();
} }
static void testSplit() {
// Test various split methods
Tests::spawnKitty("a");
// these must not crash
EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok");
Tests::spawnKitty("b");
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg splitratio -0.2"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 743,1036");
}
OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1495,1036");
}
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok");
OK(getFromSocket("/dispatch layoutmsg togglesplit"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,823");
}
OK(getFromSocket("/dispatch layoutmsg swapsplit"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,859");
EXPECT_CONTAINS(str, "size: 1876,199");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testRotatesplit() {
OK(getFromSocket("r/keyword general:gaps_in 0"));
OK(getFromSocket("r/keyword general:gaps_out 0"));
OK(getFromSocket("r/keyword general:border_size 0"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
// test 4 repeated rotations by 90 degrees
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
// test different angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit 180"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 270"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 360"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,540");
}
// test negative angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit -90"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit -180"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
OK(getFromSocket("/reload"));
}
static bool test() { static bool test() {
NLog::log("{}Testing Dwindle layout", Colors::GREEN); NLog::log("{}Testing Dwindle layout", Colors::GREEN);
@ -237,12 +91,6 @@ static bool test() {
NLog::log("{}Testing #13349", Colors::GREEN); NLog::log("{}Testing #13349", Colors::GREEN);
test13349(); test13349();
NLog::log("{}Testing splits", Colors::GREEN);
testSplit();
NLog::log("{}Testing rotatesplit", Colors::GREEN);
testRotatesplit();
// clean up // clean up
NLog::log("Cleaning up", Colors::YELLOW); NLog::log("Cleaning up", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); getFromSocket("/dispatch workspace 1");

View file

@ -127,34 +127,6 @@ static bool test() {
ret = 1; ret = 1;
} }
// test movegroupwindow: focus should follow the moved window
NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow f"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
// and backwards
NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow b"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
NLog::log("{}Disable autogrouping", Colors::YELLOW); NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false")); OK(getFromSocket("/keyword group:auto_group false"));
@ -201,99 +173,6 @@ static bool test() {
NLog::log("{}Expecting 0 windows", Colors::YELLOW); NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);
// test movewindoworgroup: direction should be respected when extracting from group
NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW);
OK(getFromSocket("/keyword group:groupbar:enabled 0"));
{
auto kittyE = Tests::spawnKitty();
if (!kittyE) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
// group kitty, and new windows should be auto-grouped
OK(getFromSocket("/dispatch togglegroup"));
auto kittyF = Tests::spawnKitty();
if (!kittyF) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
EXPECT(Tests::windowCount(), 2);
// both windows should be grouped at the same position
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
// move active window out of group to the right
NLog::log("{}Test movewindoworgroup r", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup r"));
// the group should stay at x=22, the extracted window should be to the right
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
}
// move it back into the group
OK(getFromSocket("/dispatch moveintogroup l"));
// move active window out of group downward
NLog::log("{}Test movewindoworgroup d", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup d"));
// the group should stay at y=22, the extracted window should be below
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
}
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
}
// test that we deny a floated window getting auto-grouped into a tiled group.
NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN);
OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled"));
OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes"));
auto kittyProcE = Tests::spawnKitty("kitty_tiled");
if (!kittyProcE) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes"));
auto kittyProcF = Tests::spawnKitty("kitty_floated");
if (!kittyProcF) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
EXPECT(Tests::windowCount(), 2);
{
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: kitty_floated");
if (classPos == std::string::npos) {
NLog::log("{}Could not find kitty_floated in clients output", Colors::RED);
ret = 1;
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "floating: 1");
EXPECT_CONTAINS(windowEntry, "grouped: 0");
}
}
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
return !ret; return !ret;
} }

View file

@ -1,53 +0,0 @@
#include "../../Log.hpp"
#include "../shared.hpp"
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static bool spawnLayer(const std::string& namespace_) {
NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_);
if (!Tests::spawnLayerKitty(namespace_)) {
NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_);
return false;
}
return true;
}
static bool test() {
NLog::log("{}Testing plugin layerrules", Colors::GREEN);
if (!spawnLayer("rule-layer"))
return false;
OK(getFromSocket("/dispatch plugin:test:add_layer_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect"));
if (!spawnLayer("rule-layer"))
return false;
if (!spawnLayer("norule-layer"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_layer_rule"));
OK(getFromSocket("/reload"));
NLog::log("{}Killing all layers", Colors::YELLOW);
Tests::killAllLayers();
NLog::log("{}Expecting 0 layers", Colors::YELLOW);
EXPECT(Tests::layerCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

View file

@ -1,127 +0,0 @@
#include "../shared.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "tests.hpp"
static int ret = 0;
static void swar() {
OK(getFromSocket("/keyword layout:single_window_aspect_ratio 1 1"));
Tests::spawnKitty();
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 442,22");
EXPECT_CONTAINS(str, "size: 1036,1036");
}
Tests::spawnKitty();
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 442,22");
EXPECT_CONTAINS(str, "size: 1036,1036");
}
// don't use swar on maximized
OK(getFromSocket("/dispatch fullscreen 1"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
// Don't crash when focus after global geometry changes
static void testCrashOnGeomUpdate() {
Tests::spawnKitty();
Tests::spawnKitty();
Tests::spawnKitty();
// move the layout
OK(getFromSocket("/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1"));
// shouldnt crash
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
// Test if size + pos is preserved after fs cycle
static void testPosPreserve() {
Tests::spawnKitty();
OK(getFromSocket("/dispatch setfloating class:kitty"));
OK(getFromSocket("/dispatch resizewindowpixel exact 1337 69, class:kitty"));
OK(getFromSocket("/dispatch movewindowpixel exact 420 420, class:kitty"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 420,420");
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch fullscreen"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch movewindow r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 581,420");
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch fullscreen"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 581,420");
EXPECT_CONTAINS(str, "size: 1337,69");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing layout generic", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace 10"));
// test
NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN);
swar();
testCrashOnGeomUpdate();
testPosPreserve();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test);

View file

@ -97,67 +97,6 @@ static void focusMasterPrevious() {
Tests::killAllWindows(); Tests::killAllWindows();
} }
static void testFsBehavior() {
// Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen
// check that it doesn't.
for (auto const& win : {"master", "slave1", "slave2"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:master"));
OK(getFromSocket("/dispatch fullscreen 1"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: master");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
Tests::spawnKitty("new_master");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: new_master");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
Tests::spawnKitty("ignored");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: new_master");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
Tests::spawnKitty("vaxwashere");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: vaxwashere");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() { static bool test() {
NLog::log("{}Testing Master layout", Colors::GREEN); NLog::log("{}Testing Master layout", Colors::GREEN);
@ -169,9 +108,6 @@ static bool test() {
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
focusMasterPrevious(); focusMasterPrevious();
NLog::log("{}Testing fs behavior", Colors::GREEN);
testFsBehavior();
// clean up // clean up
NLog::log("Cleaning up", Colors::YELLOW); NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1")); OK(getFromSocket("/dispatch workspace 1"));

View file

@ -1,228 +0,0 @@
#include "../shared.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "tests.hpp"
static int ret = 0;
static void testFocusCycling() {
for (auto const& win : {"a", "b", "c", "d"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch movewindow l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch movefocus u"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testFocusWrapping() {
for (auto const& win : {"a", "b", "c", "d"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
// set wrap_focus to true
OK(getFromSocket("/keyword scrolling:wrap_focus true"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch layoutmsg focus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: a");
}
// set wrap_focus to false
OK(getFromSocket("/keyword scrolling:wrap_focus false"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: a");
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testSwapcolWrapping() {
for (auto const& win : {"a", "b", "c", "d"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
// set wrap_swapcol to true
OK(getFromSocket("/keyword scrolling:wrap_swapcol true"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
for (auto const& win : {"a", "b", "c", "d"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
for (auto const& win : {"a", "b", "c", "d"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
// set wrap_swapcol to false
OK(getFromSocket("/keyword scrolling:wrap_swapcol false"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing Scroll layout", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace name:scroll"));
OK(getFromSocket("/keyword general:layout scrolling"));
// test
NLog::log("{}Testing focus cycling", Colors::GREEN);
testFocusCycling();
// test
NLog::log("{}Testing focus wrap", Colors::GREEN);
testFocusWrapping();
// test
NLog::log("{}Testing swapcol wrap", Colors::GREEN);
testSwapcolWrapping();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test);

View file

@ -93,9 +93,9 @@ static void testSwapWindow() {
{ {
getFromSocket("/dispatch focuswindow class:kitty_A"); getFromSocket("/dispatch focuswindow class:kitty_A");
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch swapwindow r")); OK(getFromSocket("/dispatch swapwindow l"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B")); OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
@ -566,98 +566,6 @@ static bool testWindowRuleFocusOnActivate() {
return true; return true;
} }
// tests if a pinned window contains the valid workspace after change
static bool testPinnedWorkspacesValid() {
OK(getFromSocket("/reload"));
getFromSocket("/dispatch workspace 1337");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating class:kitty"));
OK(getFromSocket("/dispatch pin class:kitty"));
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1337"), true);
EXPECT(str.contains("pinned: 1"), true);
}
getFromSocket("/dispatch workspace 1338");
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1338"), true);
EXPECT(str.contains("pinned: 1"), true);
}
OK(getFromSocket("/dispatch settiled class:kitty"))
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1338"), true);
EXPECT(str.contains("pinned: 0"), true);
}
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return true;
}
static bool testWindowRuleWorkspaceEmpty() {
NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class kitty_A, workspace empty"));
OK(getFromSocket("/keyword windowrule match:class kitty_B, workspace emptyn"));
getFromSocket("/dispatch workspace 3");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 3"), true);
}
if (!spawnKitty("kitty_A")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1"), true);
}
getFromSocket("/dispatch workspace 3");
if (!spawnKitty("kitty_B")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 4"), true);
}
Tests::killAllWindows();
return true;
}
static bool test() { static bool test() {
NLog::log("{}Testing windows", Colors::GREEN); NLog::log("{}Testing windows", Colors::GREEN);
@ -1086,7 +994,7 @@ static bool test() {
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
@ -1094,12 +1002,12 @@ static bool test() {
if (!spawnKitty("plugin_kitty")) if (!spawnKitty("plugin_kitty"))
return false; return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
@ -1108,7 +1016,7 @@ static bool test() {
if (!spawnKitty("plugin_kitty")) if (!spawnKitty("plugin_kitty"))
return false; return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
@ -1120,8 +1028,6 @@ static bool test() {
testGroupFallbackFocus(); testGroupFallbackFocus();
testInitialFloatSize(); testInitialFloatSize();
testWindowRuleFocusOnActivate(); testWindowRuleFocusOnActivate();
testPinnedWorkspacesValid();
testWindowRuleWorkspaceEmpty();
NLog::log("{}Reloading config", Colors::YELLOW); NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));

View file

@ -255,144 +255,6 @@ static void testMultimonBAF() {
Tests::killAllWindows(); Tests::killAllWindows();
} }
static void testMultimonFocus() {
NLog::log("{}Testing multimon focus and move", Colors::YELLOW);
OK(getFromSocket("/keyword input:follow_mouse 0"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 8"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 7"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 7 ");
}
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
Tests::spawnKitty("c");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 7 ");
}
OK(getFromSocket("/dispatch movewindow r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/keyword general:no_focus_fallback true"));
OK(getFromSocket("/keyword binds:window_direction_monitor_fallback false"));
EXPECT_NOT(getFromSocket("/dispatch movefocus l"), "ok");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movewindow l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
}
static void testDynamicWsEffects() {
// test dynamic workspace effects, they shouldn't lag
OK(getFromSocket("/dispatch workspace 69"));
Tests::spawnKitty("bitch");
OK(getFromSocket("r/keyword workspace 69,bordersize:20"));
OK(getFromSocket("r/keyword workspace 69,rounding:false"));
EXPECT(getFromSocket("/getprop class:bitch border_size"), "20");
EXPECT(getFromSocket("/getprop class:bitch rounding"), "0");
OK(getFromSocket("/reload"));
Tests::killAllWindows();
}
static bool test() { static bool test() {
NLog::log("{}Testing workspaces", Colors::GREEN); NLog::log("{}Testing workspaces", Colors::GREEN);
@ -732,14 +594,13 @@ static bool test() {
Tests::killAllWindows(); Tests::killAllWindows();
testMultimonBAF(); testMultimonBAF();
testMultimonFocus();
// destroy the headless output // destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3")); OK(getFromSocket("/output remove HEADLESS-3"));
testSpecialWorkspaceFullscreen(); testSpecialWorkspaceFullscreen();
testAsymmetricGaps(); testAsymmetricGaps();
testDynamicWsEffects();
NLog::log("{}Expecting 0 windows", Colors::YELLOW); NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);

View file

@ -3,7 +3,6 @@
#include <cerrno> #include <cerrno>
#include <thread> #include <thread>
#include <print> #include <print>
#include <fstream>
#include "../shared.hpp" #include "../shared.hpp"
#include "../hyprctlCompat.hpp" #include "../hyprctlCompat.hpp"
@ -40,38 +39,6 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
return kitty; return kitty;
} }
CUniquePointer<CProcess> Tests::spawnLayerKitty(const std::string& namespace_, const std::vector<std::string> args) {
std::vector<std::string> programArgs = args;
if (!namespace_.empty()) {
programArgs.insert(programArgs.begin(), "--class");
programArgs.insert(programArgs.begin() + 1, namespace_);
}
programArgs.insert(programArgs.begin(), "+kitten");
programArgs.insert(programArgs.begin() + 1, "panel");
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
kitty->runAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// wait while the layer spawns
int counter = 0;
while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50)
return nullptr;
}
if (!processAlive(kitty->pid()))
return nullptr;
return kitty;
}
bool Tests::processAlive(pid_t pid) { bool Tests::processAlive(pid_t pid) {
errno = 0; errno = 0;
int ret = kill(pid, 0); int ret = kill(pid, 0);
@ -129,38 +96,6 @@ void Tests::waitUntilWindowsN(int n) {
} }
} }
int Tests::layerCount() {
return countOccurrences(getFromSocket("/layers"), "namespace: ");
}
bool Tests::killAllLayers() {
auto str = getFromSocket("/layers");
auto pos = str.find("pid: ");
while (pos != std::string::npos) {
auto pid = stoi(str.substr(pos + 5, str.find('\n', pos)));
kill(pid, 15);
// we need to wait for a bit because for some reason otherwise we'll end up
// with layers with pid -1 if they are all removed at the same time
std::this_thread::sleep_for(std::chrono::milliseconds(100));
pos = str.find("pid: ", pos + 5);
}
int counter = 0;
while (Tests::layerCount() != 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
std::println("{}Timed out waiting for layers to close", Colors::RED);
return false;
}
}
return true;
}
std::string Tests::execAndGet(const std::string& cmd) { std::string Tests::execAndGet(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd}); CProcess proc("/bin/sh", {"-c", cmd});
@ -170,14 +105,3 @@ std::string Tests::execAndGet(const std::string& cmd) {
return proc.stdOut(); return proc.stdOut();
} }
bool Tests::writeFile(const std::string& name, const std::string& contents) {
std::ofstream of(name, std::ios::trunc);
if (!of.good())
return false;
of << contents;
of.close();
return true;
}

View file

@ -9,14 +9,10 @@
//NOLINTNEXTLINE //NOLINTNEXTLINE
namespace Tests { namespace Tests {
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {}); Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {});
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnLayerKitty(const std::string& namespace_ = "", const std::vector<std::string> args = {});
bool processAlive(pid_t pid); bool processAlive(pid_t pid);
int windowCount(); int windowCount();
int countOccurrences(const std::string& in, const std::string& what); int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows(); bool killAllWindows();
void waitUntilWindowsN(int n); void waitUntilWindowsN(int n);
int layerCount();
bool killAllLayers();
std::string execAndGet(const std::string& cmd); std::string execAndGet(const std::string& cmd);
bool writeFile(const std::string& name, const std::string& contents);
}; };

View file

@ -179,17 +179,6 @@ master {
new_status = master new_status = master
} }
scrolling {
fullscreen_on_one_column = true
column_width = 0.5
focus_fit_method = 1
follow_focus = true
follow_min_visible = 1
explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0
wrap_focus = true
wrap_swapcol = true
}
# https://wiki.hyprland.org/Configuring/Variables/#misc # https://wiki.hyprland.org/Configuring/Variables/#misc
misc { misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
@ -250,7 +239,7 @@ bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating, bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys # Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l bind = $mainMod, left, movefocus, l

View file

@ -12,7 +12,6 @@
epoll-shim, epoll-shim,
git, git,
glaze-hyprland, glaze-hyprland,
glslang,
gtest, gtest,
hyprcursor, hyprcursor,
hyprgraphics, hyprgraphics,
@ -22,7 +21,6 @@
hyprutils, hyprutils,
hyprwayland-scanner, hyprwayland-scanner,
hyprwire, hyprwire,
lcms2,
libGL, libGL,
libdrm, libdrm,
libexecinfo, libexecinfo,
@ -66,20 +64,8 @@
inherit (builtins) foldl' readFile; inherit (builtins) foldl' readFile;
inherit (lib.asserts) assertMsg; inherit (lib.asserts) assertMsg;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit inherit (lib.lists) flatten concatLists optional optionals;
(lib.lists) inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
flatten
concatLists
optional
optionals
;
inherit
(lib.strings)
makeBinPath
optionalString
cmakeBool
trim
;
fs = lib.fileset; fs = lib.fileset;
adapters = flatten [ adapters = flatten [
@ -91,8 +77,7 @@
in in
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert assertMsg (!hidpiXWayland) assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
"The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.";
assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
customStdenv.mkDerivation (finalAttrs: { customStdenv.mkDerivation (finalAttrs: {
@ -105,29 +90,24 @@ in
fs.intersection fs.intersection
# allows non-flake builds to only include files tracked by git # allows non-flake builds to only include files tracked by git
(fs.gitTracked ../.) (fs.gitTracked ../.)
( (fs.unions (flatten [
fs.unions (flatten [ ../assets/hyprland-portals.conf
../assets/hyprland-portals.conf ../assets/install
../assets/install ../hyprctl
../hyprctl ../hyprland.pc.in
../hyprland.pc.in ../hyprpm
../hyprpm ../LICENSE
../LICENSE ../protocols
../protocols ../src
../src ../start
../start ../systemd
../systemd ../VERSION
../VERSION (fs.fileFilter (file: file.hasExt "1") ../docs)
(fs.fileFilter (file: file.hasExt "1") ../docs) (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example) (fs.fileFilter (file: file.hasExt "sh") ../scripts)
(fs.fileFilter (file: file.hasExt "sh") ../scripts) (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) (optional withTests [../tests ../hyprtester])
(optional withTests [ ]));
../tests
../hyprtester
])
])
);
}; };
postPatch = '' postPatch = ''
@ -143,10 +123,7 @@ in
GIT_COMMITS = revCount; GIT_COMMITS = revCount;
GIT_COMMIT_DATE = date; GIT_COMMIT_DATE = date;
GIT_COMMIT_HASH = commit; GIT_COMMIT_HASH = commit;
GIT_DIRTY = GIT_DIRTY = if (commit == "") then "clean" else "dirty";
if (commit == "")
then "clean"
else "dirty";
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
}; };
@ -174,7 +151,6 @@ in
cairo cairo
git git
glaze-hyprland glaze-hyprland
glslang
gtest gtest
hyprcursor hyprcursor
hyprgraphics hyprgraphics
@ -182,7 +158,6 @@ in
hyprlang hyprlang
hyprutils hyprutils
hyprwire hyprwire
lcms2
libdrm libdrm
libgbm libgbm
libGL libGL
@ -243,14 +218,12 @@ in
postInstall = '' postInstall = ''
${optionalString wrapRuntimeDeps '' ${optionalString wrapRuntimeDeps ''
wrapProgram $out/bin/Hyprland \ wrapProgram $out/bin/Hyprland \
--suffix PATH : ${ --suffix PATH : ${makeBinPath [
makeBinPath [ binutils
binutils hyprland-guiutils
hyprland-guiutils pciutils
pciutils pkgconf
pkgconf ]}
]
}
''} ''}
${optionalString withTests '' ${optionalString withTests ''

View file

@ -2,7 +2,7 @@
writeShellApplication, writeShellApplication,
deadnix, deadnix,
statix, statix,
nixfmt, alejandra,
llvmPackages_19, llvmPackages_19,
fd, fd,
}: }:
@ -11,7 +11,7 @@ writeShellApplication {
runtimeInputs = [ runtimeInputs = [
deadnix deadnix
statix statix
nixfmt alejandra
llvmPackages_19.clang-tools llvmPackages_19.clang-tools
fd fd
]; ];
@ -24,14 +24,14 @@ writeShellApplication {
nix_format() { nix_format() {
if [ "$*" = 0 ]; then if [ "$*" = 0 ]; then
fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \;
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \; fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \;
elif [ -d "$1" ]; then elif [ -d "$1" ]; then
fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \;
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \;
else else
statix fix -- "$1" statix fix -- "$1"
deadnix -e "$1" deadnix -e "$1"
nixfmt "$1" alejandra "$1"
fi fi
} }

View file

@ -1,15 +1,13 @@
self: self: {
{ config,
lib, lib,
pkgs, pkgs,
... ...
}: }: let
let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
package = self.packages.${system}.default; package = self.packages.${system}.default;
in in {
{
config = { config = {
wayland.windowManager.hyprland.package = lib.mkDefault package; wayland.windowManager.hyprland.package = lib.mkDefault package;
}; };

View file

@ -1,5 +1,4 @@
lib: lib: let
let
inherit (lib) inherit (lib)
attrNames attrNames
filterAttrs filterAttrs
@ -82,51 +81,44 @@ let
::: :::
*/ */
toHyprlang = toHyprlang = {
{ topCommandsPrefixes ? ["$" "bezier"],
topCommandsPrefixes ? [ bottomCommandsPrefixes ? [],
"$" }: attrs: let
"bezier" toHyprlang' = attrs: let
], # Specially configured `toKeyValue` generator with support for duplicate keys
bottomCommandsPrefixes ? [ ], # and a legible key-value separator.
}: mkCommands = generators.toKeyValue {
attrs: mkKeyValue = generators.mkKeyValueDefault {} " = ";
let listsAsDuplicateKeys = true;
toHyprlang' = indent = ""; # No indent, since we don't have nesting
attrs: };
let
# Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator.
mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault { } " = ";
listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting
};
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
# Uses `flattenAttrs` with a colon separator. # Uses `flattenAttrs` with a colon separator.
commands = flattenAttrs (p: k: "${p}:${k}") attrs; commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# General filtering function to check if a key starts with any prefix in a given list. # General filtering function to check if a key starts with any prefix in a given list.
filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; filterCommands = list: n:
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# Partition keys into top commands and the rest # Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands); result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
remainingCommands = removeAttrs commands result.right; remainingCommands = removeAttrs commands result.right;
# Partition remaining commands into bottom commands and regular commands # Partition remaining commands into bottom commands and regular commands
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
regularCommands = removeAttrs remainingCommands result2.right; regularCommands = removeAttrs remainingCommands result2.right;
in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
in in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
in
toHyprlang' attrs; toHyprlang' attrs;
/** /**
@ -182,21 +174,26 @@ let
``` ```
::: :::
*/ */
flattenAttrs = flattenAttrs = pred: attrs: let
pred: attrs: flattenAttrs' = prefix: attrs:
let builtins.foldl' (
flattenAttrs' = acc: key: let
prefix: attrs: value = attrs.${key};
builtins.foldl' ( newKey =
acc: key: if prefix == ""
let then key
value = attrs.${key}; else pred prefix key;
newKey = if prefix == "" then key else pred prefix key; in
in acc
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) // (
) { } (builtins.attrNames attrs); if builtins.isAttrs value
in then flattenAttrs' newKey value
else {"${newKey}" = value;}
)
) {} (builtins.attrNames attrs);
in
flattenAttrs' "" attrs; flattenAttrs' "" attrs;
in in
{ {

View file

@ -1,21 +1,18 @@
inputs: inputs: {
{
config, config,
lib, lib,
pkgs, pkgs,
... ...
}: }: let
let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
selflib = import ./lib.nix lib; selflib = import ./lib.nix lib;
cfg = config.programs.hyprland; cfg = config.programs.hyprland;
in in {
{
options = { options = {
programs.hyprland = { programs.hyprland = {
plugins = lib.mkOption { plugins = lib.mkOption {
type = with lib.types; listOf (either package path); type = with lib.types; listOf (either package path);
default = [ ]; default = [];
description = '' description = ''
List of Hyprland plugins to use. Can either be packages or List of Hyprland plugins to use. Can either be packages or
absolute plugin paths. absolute plugin paths.
@ -23,25 +20,23 @@ in
}; };
settings = lib.mkOption { settings = lib.mkOption {
type = type = with lib.types; let
with lib.types; valueType =
let nullOr (oneOf [
valueType = bool
nullOr (oneOf [ int
bool float
int str
float path
str (attrsOf valueType)
path (listOf valueType)
(attrsOf valueType) ])
(listOf valueType) // {
]) description = "Hyprland configuration value";
// { };
description = "Hyprland configuration value"; in
};
in
valueType; valueType;
default = { }; default = {};
description = '' description = ''
Hyprland configuration written in Nix. Entries with the same key Hyprland configuration written in Nix. Entries with the same key
should be written as lists. Variables' and colors' names should be should be written as lists. Variables' and colors' names should be
@ -97,15 +92,8 @@ in
topPrefixes = lib.mkOption { topPrefixes = lib.mkOption {
type = with lib.types; listOf str; type = with lib.types; listOf str;
default = [ default = ["$" "bezier"];
"$" example = ["$" "bezier" "source"];
"bezier"
];
example = [
"$"
"bezier"
"source"
];
description = '' description = ''
List of prefix of attributes to put at the top of the config. List of prefix of attributes to put at the top of the config.
''; '';
@ -113,8 +101,8 @@ in
bottomPrefixes = lib.mkOption { bottomPrefixes = lib.mkOption {
type = with lib.types; listOf str; type = with lib.types; listOf str;
default = [ ]; default = [];
example = [ "source" ]; example = ["source"];
description = '' description = ''
List of prefix of attributes to put at the bottom of the config. List of prefix of attributes to put at the bottom of the config.
''; '';
@ -129,36 +117,35 @@ in
}; };
} }
(lib.mkIf cfg.enable { (lib.mkIf cfg.enable {
environment.etc."xdg/hypr/hyprland.conf" = environment.etc."xdg/hypr/hyprland.conf" = let
let shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ];
pluginsToHyprlang = pluginsToHyprlang = plugins:
_plugins: selflib.toHyprlang {
selflib.toHyprlang topCommandsPrefixes = cfg.topPrefixes;
{ bottomCommandsPrefixes = cfg.bottomPrefixes;
}
{
"exec-once" = let
mkEntry = entry:
if lib.types.package.check entry
then "${entry}/lib/lib${entry.pname}.so"
else entry;
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
in
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
};
in
lib.mkIf shouldGenerate {
text =
lib.optionalString (cfg.plugins != [])
(pluginsToHyprlang cfg.plugins)
+ lib.optionalString (cfg.settings != {})
(selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes; topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes;
} }
{ cfg.settings)
"exec-once" =
let
mkEntry =
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry;
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
in
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
};
in
lib.mkIf shouldGenerate {
text =
lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins)
+ lib.optionalString (cfg.settings != { }) (
selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes;
} cfg.settings
)
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
}; };
}) })

View file

@ -2,27 +2,20 @@
self, self,
lib, lib,
inputs, inputs,
}: }: let
let mkDate = longDate: (lib.concatStringsSep "-" [
mkDate = (builtins.substring 0 4 longDate)
longDate: (builtins.substring 4 2 longDate)
(lib.concatStringsSep "-" [ (builtins.substring 6 2 longDate)
(builtins.substring 0 4 longDate) ]);
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in in {
{
# Contains what a user is most likely to care about: # Contains what a user is most likely to care about:
# Hyprland itself, XDPH and the Share Picker. # Hyprland itself, XDPH and the Share Picker.
default = lib.composeManyExtensions ( default = lib.composeManyExtensions (with self.overlays; [
with self.overlays; hyprland-packages
[ hyprland-extras
hyprland-packages ]);
hyprland-extras
]
);
# Packages for variations of Hyprland, dependencies included. # Packages for variations of Hyprland, dependencies included.
hyprland-packages = lib.composeManyExtensions [ hyprland-packages = lib.composeManyExtensions [
@ -40,45 +33,49 @@ in
self.overlays.glaze self.overlays.glaze
# Hyprland packages themselves # Hyprland packages themselves
( (final: _prev: let
final: _prev: date = mkDate (self.lastModifiedDate or "19700101");
let version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
date = mkDate (self.lastModifiedDate or "19700101"); in {
version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; hyprland = final.callPackage ./default.nix {
in stdenv = final.gcc15Stdenv;
{ commit = self.rev or "";
hyprland = final.callPackage ./default.nix { revCount = self.sourceInfo.revCount or "";
stdenv = final.gcc15Stdenv; inherit date version;
commit = self.rev or ""; };
revCount = self.sourceInfo.revCount or ""; hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
inherit date version;
};
hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; };
hyprland-with-tests = final.hyprland.override { withTests = true; }; hyprland-with-tests = final.hyprland.override {withTests = true;};
hyprland-with-hyprtester = builtins.trace '' hyprland-with-hyprtester =
builtins.trace ''
hyprland-with-hyprtester was removed. Please use the hyprland package. hyprland-with-hyprtester was removed. Please use the hyprland package.
Hyprtester is always built now. Hyprtester is always built now.
'' final.hyprland; ''
final.hyprland;
# deprecated packages # deprecated packages
hyprland-legacy-renderer = builtins.trace '' hyprland-legacy-renderer =
builtins.trace ''
hyprland-legacy-renderer was removed. Please use the hyprland package. hyprland-legacy-renderer was removed. Please use the hyprland package.
Legacy renderer is no longer supported. Legacy renderer is no longer supported.
'' final.hyprland; ''
final.hyprland;
hyprland-nvidia = builtins.trace '' hyprland-nvidia =
builtins.trace ''
hyprland-nvidia was removed. Please use the hyprland package. hyprland-nvidia was removed. Please use the hyprland package.
Nvidia patches are no longer needed. Nvidia patches are no longer needed.
'' final.hyprland; ''
final.hyprland;
hyprland-hidpi = builtins.trace '' hyprland-hidpi =
builtins.trace ''
hyprland-hidpi was removed. Please use the hyprland package. hyprland-hidpi was removed. Please use the hyprland package.
For more information, refer to https://wiki.hypr.land/Configuring/XWayland. For more information, refer to https://wiki.hypr.land/Configuring/XWayland.
'' final.hyprland; ''
} final.hyprland;
) })
]; ];
# Debug # Debug
@ -86,10 +83,10 @@ in
# Dependencies # Dependencies
self.overlays.hyprland-packages self.overlays.hyprland-packages
(_final: prev: { (final: prev: {
aquamarine = prev.aquamarine.override { debug = true; }; aquamarine = prev.aquamarine.override {debug = true;};
hyprutils = prev.hyprutils.override { debug = true; }; hyprutils = prev.hyprutils.override {debug = true;};
hyprland-debug = prev.hyprland.override { debug = true; }; hyprland-debug = prev.hyprland.override {debug = true;};
}) })
]; ];
@ -103,23 +100,21 @@ in
# this version is the one used in the git submodule, and allows us to # this version is the one used in the git submodule, and allows us to
# fetch the source without '?submodules=1' # fetch the source without '?submodules=1'
udis86 = final: prev: { udis86 = final: prev: {
udis86-hyprland = prev.udis86.overrideAttrs ( udis86-hyprland = prev.udis86.overrideAttrs (_self: _super: {
_self: _super: { src = final.fetchFromGitHub {
src = final.fetchFromGitHub { owner = "canihavesomecoffee";
owner = "canihavesomecoffee"; repo = "udis86";
repo = "udis86"; rev = "5336633af70f3917760a6d441ff02d93477b0c86";
rev = "5336633af70f3917760a6d441ff02d93477b0c86"; hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g="; };
};
patches = [ ]; patches = [];
} });
);
}; };
# Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true. # Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true.
# Since we don't include openssl, the build failes without the `enableSSL = false;` override # Since we don't include openssl, the build failes without the `enableSSL = false;` override
glaze = _final: prev: { glaze = final: prev: {
glaze-hyprland = prev.glaze.override { glaze-hyprland = prev.glaze.override {
enableSSL = false; enableSSL = false;
enableInterop = false; enableInterop = false;

View file

@ -1,76 +1,72 @@
inputs: pkgs: inputs: pkgs: let
let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland-with-tests; hyprland = flake.hyprland-with-tests;
in in {
{
tests = pkgs.testers.runNixOSTest { tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests"; name = "hyprland-tests";
nodes.machine = nodes.machine = {pkgs, ...}: {
{ pkgs, ... }: environment.systemPackages = with pkgs; [
{ # Programs needed for tests
environment.systemPackages = with pkgs; [ jq
# Programs needed for tests kitty
jq wl-clipboard
kitty xeyes
wl-clipboard ];
xeyes
];
# Enabled by default for some reason # Enabled by default for some reason
services.speechd.enable = false; services.speechd.enable = false;
environment.variables = { environment.variables = {
"AQ_TRACE" = "1"; "AQ_TRACE" = "1";
"HYPRLAND_TRACE" = "1"; "HYPRLAND_TRACE" = "1";
"XDG_RUNTIME_DIR" = "/tmp"; "XDG_RUNTIME_DIR" = "/tmp";
"XDG_CACHE_HOME" = "/tmp"; "XDG_CACHE_HOME" = "/tmp";
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty"; "KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
};
environment.etc."kitty/kitty.conf".text = ''
confirm_os_window_close 0
remember_window_size no
initial_window_width 640
initial_window_height 400
'';
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = [ "-vga none -device virtio-gpu-pci" ];
};
}; };
environment.etc."kitty/kitty.conf".text = ''
confirm_os_window_close 0
remember_window_size no
initial_window_width 640
initial_window_height 400
'';
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = ["-vga none -device virtio-gpu-pci"];
};
};
testScript = '' testScript = ''
# Wait for tty to be up # Wait for tty to be up
machine.wait_for_unit("multi-user.target") machine.wait_for_unit("multi-user.target")

View file

@ -15,7 +15,7 @@ echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/rend
for filename in `ls ${SHADERS_SRC}`; do for filename in `ls ${SHADERS_SRC}`; do
echo "-- ${filename}" echo "-- ${filename}"
{ echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
echo "}," >> ./src/render/shaders/Shaders.hpp echo "}," >> ./src/render/shaders/Shaders.hpp

View file

@ -47,7 +47,6 @@
#include "protocols/core/Compositor.hpp" #include "protocols/core/Compositor.hpp"
#include "protocols/core/Subcompositor.hpp" #include "protocols/core/Subcompositor.hpp"
#include "desktop/view/LayerSurface.hpp" #include "desktop/view/LayerSurface.hpp"
#include "layout/space/Space.hpp"
#include "render/Renderer.hpp" #include "render/Renderer.hpp"
#include "xwayland/XWayland.hpp" #include "xwayland/XWayland.hpp"
#include "helpers/ByteOperations.hpp" #include "helpers/ByteOperations.hpp"
@ -1405,35 +1404,6 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
PHLWINDOW leaderWindow = nullptr; PHLWINDOW leaderWindow = nullptr;
if (!useVectorAngles) { if (!useVectorAngles) {
// helper to check if two rectangles are adjacent along an axis, considering slight overlaps.
// returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension.
static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool {
constexpr double STICK_THRESHOLD = 2.0;
constexpr double MAX_OVERLAP_RATIO = 0.5;
const double aEdge = aMin;
const double bEdge = bMax;
const double delta = aEdge - bEdge;
// old STICKS check for 2px
if (std::abs(delta) < STICK_THRESHOLD)
return true;
if (delta >= 0)
return false;
const double overlap = -delta;
const double sizeA = aMax - aMin;
const double sizeB = bMax - bMin;
// reject if one rectangle fully contains the other
if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax))
return false;
// accept if overlap is at most 50% of the smaller dimension
return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO;
};
for (auto const& w : m_windows) { for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue; continue;
@ -1456,20 +1426,24 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
switch (dir) { switch (dir) {
case Math::DIRECTION_LEFT: case Math::DIRECTION_LEFT:
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break; break;
case Math::DIRECTION_RIGHT: case Math::DIRECTION_RIGHT:
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break; break;
case Math::DIRECTION_UP: case Math::DIRECTION_UP:
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break; break;
case Math::DIRECTION_DOWN: case Math::DIRECTION_DOWN:
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break; break;
default: break; default: break;
} }
@ -1825,9 +1799,6 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
g_layoutManager->recalculateMonitor(pMonitorA); g_layoutManager->recalculateMonitor(pMonitorA);
g_layoutManager->recalculateMonitor(pMonitorB); g_layoutManager->recalculateMonitor(pMonitorB);
g_pHyprRenderer->damageMonitor(pMonitorB);
g_pHyprRenderer->damageMonitor(pMonitorA);
g_pDesktopAnimationManager->setFullscreenFadeAnimation( g_pDesktopAnimationManager->setFullscreenFadeAnimation(
PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
g_pDesktopAnimationManager->setFullscreenFadeAnimation( g_pDesktopAnimationManager->setFullscreenFadeAnimation(
@ -1982,7 +1953,6 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// move the workspace // move the workspace
pWorkspace->m_monitor = pMonitor; pWorkspace->m_monitor = pMonitor;
pWorkspace->m_space->recheckWorkArea();
pWorkspace->m_events.monitorChanged.emit(); pWorkspace->m_events.monitorChanged.emit();
for (auto const& w : m_windows) { for (auto const& w : m_windows) {
@ -2038,7 +2008,6 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
pWorkspace->m_events.activeChanged.emit(); pWorkspace->m_events.activeChanged.emit();
g_layoutManager->recalculateMonitor(pMonitor); g_layoutManager->recalculateMonitor(pMonitor);
g_pHyprRenderer->damageMonitor(pMonitor);
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
pWorkspace->m_visible = true; pWorkspace->m_visible = true;
@ -2778,7 +2747,6 @@ void CCompositor::arrangeMonitors() {
} }
PROTO::xdgOutput->updateAllOutputs(); PROTO::xdgOutput->updateAllOutputs();
Event::bus()->m_events.monitor.layoutChanged.emit();
#ifndef NO_XWAYLAND #ifndef NO_XWAYLAND
const auto box = g_pCompositor->calculateX11WorkArea(); const auto box = g_pCompositor->calculateX11WorkArea();
@ -3074,27 +3042,6 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector<SWorkspace
} }
} }
void CCompositor::ensureWorkspacesOnAssignedMonitors() {
for (auto const& ws : getWorkspacesCopy()) {
if (!valid(ws) || ws->m_isSpecialWorkspace)
continue;
const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws);
if (RULE.monitor.empty())
continue;
const auto PMONITOR = getMonitorFromString(RULE.monitor);
if (!PMONITOR)
continue;
if (ws->m_monitor == PMONITOR)
continue;
Log::logger->log(Log::DEBUG, "ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}", ws->m_name, PMONITOR->m_name);
moveWorkspaceToMonitor(ws, PMONITOR, true);
}
}
std::optional<unsigned int> CCompositor::getVTNr() { std::optional<unsigned int> CCompositor::getVTNr() {
if (!m_aqBackend->hasSession()) if (!m_aqBackend->hasSession())
return std::nullopt; return std::nullopt;

View file

@ -9,7 +9,7 @@
#include "managers/KeybindManager.hpp" #include "managers/KeybindManager.hpp"
#include "managers/SessionLockManager.hpp" #include "managers/SessionLockManager.hpp"
#include "desktop/view/Window.hpp" #include "desktop/view/Window.hpp"
#include "helpers/cm/ColorManagement.hpp" #include "protocols/types/ColorManagement.hpp"
#include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/Backend.hpp>
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
@ -161,7 +161,6 @@ class CCompositor {
void updateSuspendedStates(); void updateSuspendedStates();
void onNewMonitor(SP<Aquamarine::IOutput> output); void onNewMonitor(SP<Aquamarine::IOutput> output);
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr); void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
void ensureWorkspacesOnAssignedMonitors();
std::optional<unsigned int> getVTNr(); std::optional<unsigned int> getVTNr();
bool isVRRActiveOnAnyMonitor() const; bool isVRRActiveOnAnyMonitor() const;

View file

@ -1579,18 +1579,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true}, .data = SConfigOptionDescription::SBoolData{true},
}, },
SConfigOptionDescription{
.value = "render:icc_vcgt_enabled",
.description = "Enable sending VCGT ramps to KMS with ICC profiles",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
{
.value = "render:use_shader_blur_blend",
.description = "Use experimental blurred bg blending",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/* /*
* cursor: * cursor:
@ -2105,18 +2093,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_CHOICE, .type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"}, .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"},
}, },
SConfigOptionDescription{
.value = "scrolling:wrap_focus",
.description = "Determines if column focus wraps around when going before the first column or past the last column",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{.value = true},
},
SConfigOptionDescription{
.value = "scrolling:wrap_swapcol",
.description = "Determines if column movement wraps around when moving to before the first column or past the last column",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{.value = true},
},
/* /*
* Quirks * Quirks

View file

@ -655,8 +655,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4}); registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4});
registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"}); registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"});
registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"}); registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"});
registerConfigVar("scrolling:wrap_focus", Hyprlang::INT{1});
registerConfigVar("scrolling:wrap_swapcol", Hyprlang::INT{1});
registerConfigVar("animations:enabled", Hyprlang::INT{1}); registerConfigVar("animations:enabled", Hyprlang::INT{1});
registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0});
@ -799,8 +797,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); registerConfigVar("render:non_shader_cm", Hyprlang::INT{3});
registerConfigVar("render:cm_sdr_eotf", {"default"}); registerConfigVar("render:cm_sdr_eotf", {"default"});
registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1}); registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1});
registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1});
registerConfigVar("render:use_shader_blur_blend", Hyprlang::INT{0});
registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});
registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0});
@ -875,7 +871,6 @@ CConfigManager::CConfigManager() {
m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0});
m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1});
m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1});
m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""});
// windowrule v3 // windowrule v3
m_config->addSpecialCategory("windowrule", {.key = "name"}); m_config->addSpecialCategory("windowrule", {.key = "name"});
@ -1262,10 +1257,6 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
if (VAL && VAL->m_bSetByUser) if (VAL && VAL->m_bSetByUser)
parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue()); parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str());
if (VAL && VAL->m_bSetByUser)
parser.rule().iccFile = std::any_cast<Hyprlang::STRING>(VAL->getValue());
auto newrule = parser.rule(); auto newrule = parser.rule();
std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });
@ -2284,15 +2275,6 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) {
return true; return true;
} }
bool CMonitorRuleParser::parseICC(const std::string& val) {
if (val.empty()) {
m_error += "invalid icc ";
return false;
}
m_rule.iccFile = val;
return true;
}
void CMonitorRuleParser::setDisabled() { void CMonitorRuleParser::setDisabled() {
m_rule.disabled = true; m_rule.disabled = true;
} }
@ -2387,9 +2369,6 @@ std::optional<std::string> CConfigManager::handleMonitor(const std::string& comm
} else if (ARGS[argno] == "vrr") { } else if (ARGS[argno] == "vrr") {
parser.parseVRR(std::string(ARGS[argno + 1])); parser.parseVRR(std::string(ARGS[argno + 1]));
argno++; argno++;
} else if (ARGS[argno] == "icc") {
parser.parseICC(std::string(ARGS[argno + 1]));
argno++;
} else if (ARGS[argno] == "workspace") { } else if (ARGS[argno] == "workspace") {
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1]));

View file

@ -177,7 +177,6 @@ class CMonitorRuleParser {
bool parseSDRBrightness(const std::string& value); bool parseSDRBrightness(const std::string& value);
bool parseSDRSaturation(const std::string& value); bool parseSDRSaturation(const std::string& value);
bool parseVRR(const std::string& value); bool parseVRR(const std::string& value);
bool parseICC(const std::string& value);
void setDisabled(); void setDisabled();
void setMirror(const std::string& value); void setMirror(const std::string& value);

View file

@ -2049,12 +2049,7 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques
} }
static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {
CVarList vars(request, 0, ' '); if (g_pHyprOpenGL->initShaders())
if (vars.size() > 2)
return "too many args";
if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : ""))
return format == FORMAT_JSON ? "{\"ok\": true}" : "ok"; return format == FORMAT_JSON ? "{\"ok\": true}" : "ok";
else else
return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; return format == FORMAT_JSON ? "{\"ok\": false}" : "error";
@ -2081,8 +2076,8 @@ CHyprCtl::CHyprCtl() {
registerCommand(SHyprCtlCommand{"locked", true, getIsLocked}); registerCommand(SHyprCtlCommand{"locked", true, getIsLocked});
registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions}); registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions});
registerCommand(SHyprCtlCommand{"submap", true, submapRequest}); registerCommand(SHyprCtlCommand{"submap", true, submapRequest});
registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = true, .fn = reloadShaders});
registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = false, .fn = reloadShaders});
registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest}); registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest});
registerCommand(SHyprCtlCommand{"reload", false, reloadRequest}); registerCommand(SHyprCtlCommand{"reload", false, reloadRequest});
registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin}); registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin});
@ -2202,15 +2197,6 @@ std::string CHyprCtl::getReply(std::string request) {
Desktop::Rule::ruleEngine()->updateAllRules(); Desktop::Rule::ruleEngine()->updateAllRules();
} }
for (const auto& ws : g_pCompositor->getWorkspaces()) {
if (!ws)
continue;
ws->updateWindows();
ws->updateWindowData();
ws->updateWindowDecos();
}
for (auto const& m : g_pCompositor->m_monitors) { for (auto const& m : g_pCompositor->m_monitors) {
g_pHyprRenderer->damageMonitor(m); g_pHyprRenderer->damageMonitor(m);
} }
@ -2224,38 +2210,16 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) {
} }
static bool successWrite(int fd, const std::string& data, bool needLog = true) { static bool successWrite(int fd, const std::string& data, bool needLog = true) {
size_t totalWritten = 0; if (write(fd, data.c_str(), data.length()) > 0)
size_t remaining = data.length(); return true;
size_t waitsDone = 0;
constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms
while (totalWritten < data.length()) { if (errno == EAGAIN)
ssize_t written = write(fd, data.c_str() + totalWritten, remaining); return true;
if (waitsDone > MAX_WAITS) { if (needLog)
Log::logger->log(Log::ERR, "Couldn't write to socket. Buffer was full and the client couldn't read in time."); Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
return false;
}
if (written < 0) { return false;
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// socket buffer full, wait a bit and retry
std::this_thread::sleep_for(std::chrono::microseconds(100));
waitsDone++;
continue;
}
if (needLog)
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: {}", strerror(errno));
return false;
}
waitsDone = 0;
totalWritten += written;
remaining -= written;
}
return true;
} }
static void runWritingDebugLogThread(const int conn) { static void runWritingDebugLogThread(const int conn) {

View file

@ -8,7 +8,7 @@
#include "../desktop/state/FocusState.hpp" #include "../desktop/state/FocusState.hpp"
CHyprDebugOverlay::CHyprDebugOverlay() { CHyprDebugOverlay::CHyprDebugOverlay() {
m_texture = g_pHyprRenderer->createTexture(); m_texture = makeShared<CTexture>();
} }
void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {
@ -259,7 +259,15 @@ void CHyprDebugOverlay::draw() {
cairo_surface_flush(m_cairoSurface); cairo_surface_flush(m_cairoSurface);
// copy the data to an OpenGL texture we have // copy the data to an OpenGL texture we have
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); const auto DATA = cairo_image_surface_get_data(m_cairoSurface);
m_texture->allocate();
m_texture->bind();
m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
CTexPassElement::SRenderData data; CTexPassElement::SRenderData data;
data.tex = m_texture; data.tex = m_texture;

View file

@ -42,7 +42,7 @@ class CHyprDebugOverlay {
cairo_surface_t* m_cairoSurface = nullptr; cairo_surface_t* m_cairoSurface = nullptr;
cairo_t* m_cairo = nullptr; cairo_t* m_cairo = nullptr;
SP<ITexture> m_texture; SP<CTexture> m_texture;
friend class CHyprMonitorDebugOverlay; friend class CHyprMonitorDebugOverlay;
friend class CHyprRenderer; friend class CHyprRenderer;

View file

@ -28,6 +28,8 @@ CHyprNotificationOverlay::CHyprNotificationOverlay() {
g_pHyprRenderer->damageBox(m_lastDamage); g_pHyprRenderer->damageBox(m_lastDamage);
}); });
m_texture = makeShared<CTexture>();
} }
CHyprNotificationOverlay::~CHyprNotificationOverlay() { CHyprNotificationOverlay::~CHyprNotificationOverlay() {
@ -230,7 +232,16 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {
m_lastDamage = damage; m_lastDamage = damage;
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); // copy the data to an OpenGL texture we have
const auto DATA = cairo_image_surface_get_data(m_cairoSurface);
m_texture->allocate();
m_texture->bind();
m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
CTexPassElement::SRenderData data; CTexPassElement::SRenderData data;
data.tex = m_texture; data.tex = m_texture;

View file

@ -18,7 +18,7 @@ enum eIconBackend : uint8_t {
static const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = { static const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = {
std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""},
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}}; std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0}, static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0},
CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0}, CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0},
CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0}, CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},
CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0}, CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0},
@ -57,7 +57,7 @@ class CHyprNotificationOverlay {
PHLMONITORREF m_lastMonitor; PHLMONITORREF m_lastMonitor;
Vector2D m_lastSize = Vector2D(-1, -1); Vector2D m_lastSize = Vector2D(-1, -1);
SP<ITexture> m_texture; SP<CTexture> m_texture;
}; };
inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay; inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;

View file

@ -4,7 +4,6 @@
#include "../../view/LayerSurface.hpp" #include "../../view/LayerSurface.hpp"
#include "../../types/OverridableVar.hpp" #include "../../types/OverridableVar.hpp"
#include "../../../helpers/MiscFunctions.hpp" #include "../../../helpers/MiscFunctions.hpp"
#include "../../../event/EventBus.hpp"
using namespace Desktop; using namespace Desktop;
using namespace Desktop::Rule; using namespace Desktop::Rule;
@ -33,38 +32,11 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> prop
UNSET(aboveLock) UNSET(aboveLock)
UNSET(ignoreAlpha) UNSET(ignoreAlpha)
UNSET(animationStyle) UNSET(animationStyle)
#undef UNSET
if (prio == Types::PRIORITY_WINDOW_RULE)
std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });
} }
void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) { void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
for (const auto& [key, effect] : rule->effects()) { for (const auto& [key, effect] : rule->effects()) {
switch (key) { switch (key) {
default: {
if (key <= LAYER_RULE_EFFECT_LAST_STATIC) {
Log::logger->log(Log::TRACE, "CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc<std::underlying_type_t<eLayerRuleEffect>>(key));
break;
}
// custom type, add to our vec
if (!m_otherProps.props.contains(key)) {
m_otherProps.props.emplace(key,
makeUnique<SCustomPropContainer>(SCustomPropContainer{
.idx = key,
.propMask = rule->getPropertiesMask(),
.effect = effect,
}));
} else {
auto& e = m_otherProps.props[key];
e->propMask |= rule->getPropertiesMask();
e->effect = effect;
}
break;
}
case LAYER_RULE_EFFECT_NONE: { case LAYER_RULE_EFFECT_NONE: {
Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
break; break;
@ -153,7 +125,4 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRulePropert
applyDynamicRule(wr); applyDynamicRule(wr);
} }
// for plugins
Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());
} }

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "LayerRuleEffectContainer.hpp"
#include "../../DesktopTypes.hpp" #include "../../DesktopTypes.hpp"
#include "../Rule.hpp" #include "../Rule.hpp"
#include "../../types/OverridableVar.hpp" #include "../../types/OverridableVar.hpp"
@ -22,17 +21,6 @@ namespace Desktop::Rule {
void propertiesChanged(std::underlying_type_t<eRuleProperty> props); void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
struct SCustomPropContainer {
CLayerRuleEffectContainer::storageType idx = LAYER_RULE_EFFECT_NONE;
std::underlying_type_t<eRuleProperty> propMask = RULE_PROP_NONE;
std::string effect;
};
// This struct holds props that were dynamically registered. Plugins may read this.
struct {
std::unordered_map<CLayerRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;
} m_otherProps;
#define COMMA , #define COMMA ,
#define DEFINE_PROP(type, name, def) \ #define DEFINE_PROP(type, name, def) \
private: \ private: \

View file

@ -97,7 +97,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false; return false;
break; break;
case RULE_PROP_CONTENT: case RULE_PROP_CONTENT:
if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType()))) if (!engine->match(w->getContentType()))
return false; return false;
break; break;
case RULE_PROP_XDG_TAG: case RULE_PROP_XDG_TAG:

View file

@ -630,8 +630,6 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProper
needsRelayout = needsRelayout || RES.needsRelayout; needsRelayout = needsRelayout || RES.needsRelayout;
} }
m_window->updateWindowData();
m_window->updateWindowDecos();
m_window->updateDecorationValues(); m_window->updateDecorationValues();
if (needsRelayout) if (needsRelayout)

View file

@ -120,7 +120,7 @@ void CGroup::add(PHLWINDOW w) {
m_target->recalc(); m_target->recalc();
} }
void CGroup::remove(PHLWINDOW w, Math::eDirection dir) { void CGroup::remove(PHLWINDOW w) {
std::optional<size_t> idx; std::optional<size_t> idx;
for (size_t i = 0; i < m_windows.size(); ++i) { for (size_t i = 0; i < m_windows.size(); ++i) {
if (m_windows.at(i) == w) { if (m_windows.at(i) == w) {
@ -156,20 +156,8 @@ void CGroup::remove(PHLWINDOW w, Math::eDirection dir) {
updateWindowVisibility(); updateWindowVisibility();
// do this here: otherwise the new current is hidden and workspace rules get wrong data // do this here: otherwise the new current is hidden and workspace rules get wrong data
if (!REMOVING_GROUP) { if (!REMOVING_GROUP)
std::optional<Vector2D> focalPoint; w->m_target->assignToSpace(m_target->space());
if (dir != Math::DIRECTION_DEFAULT) {
const auto box = m_target->position();
switch (dir) {
case Math::DIRECTION_RIGHT: focalPoint = Vector2D(box.x + box.w, box.y + box.h / 2.0); break;
case Math::DIRECTION_LEFT: focalPoint = Vector2D(box.x, box.y + box.h / 2.0); break;
case Math::DIRECTION_DOWN: focalPoint = Vector2D(box.x + box.w / 2.0, box.y + box.h); break;
case Math::DIRECTION_UP: focalPoint = Vector2D(box.x + box.w / 2.0, box.y); break;
default: break;
}
}
w->m_target->assignToSpace(m_target->space(), focalPoint);
}
} }
void CGroup::moveCurrent(bool next) { void CGroup::moveCurrent(bool next) {
@ -329,7 +317,6 @@ void CGroup::swapWithNext() {
size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1; size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1;
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
m_current = idx;
updateWindowVisibility(); updateWindowVisibility();
@ -342,7 +329,6 @@ void CGroup::swapWithLast() {
size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1; size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1;
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
m_current = idx;
updateWindowVisibility(); updateWindowVisibility();

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "../DesktopTypes.hpp" #include "../DesktopTypes.hpp"
#include "../../helpers/math/Direction.hpp"
#include <vector> #include <vector>
@ -18,7 +17,7 @@ namespace Desktop::View {
bool has(PHLWINDOW w) const; bool has(PHLWINDOW w) const;
void add(PHLWINDOW w); void add(PHLWINDOW w);
void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT); void remove(PHLWINDOW w);
void moveCurrent(bool next); void moveCurrent(bool next);
void setCurrent(size_t idx); void setCurrent(size_t idx);
void setCurrent(PHLWINDOW w); void setCurrent(PHLWINDOW w);

View file

@ -277,7 +277,7 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
// fucker fucking fuck // fucker fucking fuck
const auto WORKAREA = m_workspace->m_space->workArea(); const auto WORKAREA = m_workspace->m_space->workArea();
const auto& RESERVED = CReservedArea(PMONITOR->logicalBox(), WORKAREA); const auto& RESERVED = PMONITOR->m_reservedArea;
if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) { if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) {
POS.x -= RESERVED.left(); POS.x -= RESERVED.left();
@ -510,6 +510,12 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
setAnimationsToMove(); setAnimationsToMove();
OLDWORKSPACE->updateWindows();
OLDWORKSPACE->updateWindowData();
pWorkspace->updateWindows();
pWorkspace->updateWindowData();
g_pCompositor->updateAllWindowsAnimatedDecorationValues(); g_pCompositor->updateAllWindowsAnimatedDecorationValues();
if (valid(pWorkspace)) { if (valid(pWorkspace)) {
@ -801,13 +807,9 @@ void CWindow::updateWindowData() {
} }
void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) {
if (workspaceRule.noBorder.value_or(false)) m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE);
m_ruleApplicator->borderSize().matchOptional(std::optional<Hyprlang::INT>(0), Desktop::Types::PRIORITY_WORKSPACE_RULE);
else if (workspaceRule.borderSize)
m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE);
else
m_ruleApplicator->borderSize().matchOptional(std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE);
m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE);
m_ruleApplicator->borderSize().matchOptional(workspaceRule.noBorder ? std::optional<Hyprlang::INT>(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE);
m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional<Hyprlang::INT>(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional<Hyprlang::INT>(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE);
m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE);
} }
@ -1871,12 +1873,11 @@ void CWindow::mapWindow() {
if (WORKSPACEARGS.contains("silent")) if (WORKSPACEARGS.contains("silent"))
workspaceSilent = true; workspaceSilent = true;
auto joined = WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0); if (WORKSPACEARGS.contains("empty") && PWORKSPACE->getWindows() <= 1) {
if (joined.starts_with("empty") && PWORKSPACE->getWindows() == 0) {
requestedWorkspaceID = PWORKSPACE->m_id; requestedWorkspaceID = PWORKSPACE->m_id;
requestedWorkspaceName = PWORKSPACE->m_name; requestedWorkspaceName = PWORKSPACE->m_name;
} else { } else {
auto result = getWorkspaceIDNameFromString(joined); auto result = getWorkspaceIDNameFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0));
requestedWorkspaceID = result.id; requestedWorkspaceID = result.id;
requestedWorkspaceName = result.name; requestedWorkspaceName = result.name;
} }
@ -1948,13 +1949,11 @@ void CWindow::mapWindow() {
g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)}); g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)});
Event::bus()->m_events.window.openEarly.emit(m_self.lock()); Event::bus()->m_events.window.openEarly.emit(m_self.lock());
if (*PAUTOGROUP // auto_group enabled if (*PAUTOGROUP // auto_group enabled
&& Desktop::focusState()->window() // focused window exists && Desktop::focusState()->window() // focused window exists
&& canBeGroupedInto(Desktop::focusState()->window()->m_group) // we can group && canBeGroupedInto(Desktop::focusState()->window()->m_group) // we can group
&& Desktop::focusState()->window()->m_workspace == m_workspace // workspaces match, we're not opening on another ws && Desktop::focusState()->window()->m_workspace == m_workspace // workspaces match, we're not opening on another ws
&& !g_pXWaylandManager->shouldBeFloated(m_self.lock()) && !isX11OverrideRedirect() // not a window that should float or X11 && !isModal() && !(parent() && m_isFloating) && !isX11OverrideRedirect() // not a modal, floating child or X11 OR
&& !(m_isFloating && !Desktop::focusState()->window()->m_isFloating) // do not auto-group a floated window into a tiled group
&& !isModal() // no modal grouping
) { ) {
// add to group if we are focused on one // add to group if we are focused on one
Desktop::focusState()->window()->m_group->add(m_self.lock()); Desktop::focusState()->window()->m_group->add(m_self.lock());

View file

@ -41,7 +41,6 @@ namespace Event {
Event<PHLWINDOW> openEarly; Event<PHLWINDOW> openEarly;
Event<PHLWINDOW> destroy; Event<PHLWINDOW> destroy;
Event<PHLWINDOW> close; Event<PHLWINDOW> close;
Event<PHLWINDOW> kill;
Event<PHLWINDOW, Desktop::eFocusReason> active; Event<PHLWINDOW, Desktop::eFocusReason> active;
Event<PHLWINDOW> urgent; Event<PHLWINDOW> urgent;
Event<PHLWINDOW> title; Event<PHLWINDOW> title;
@ -55,7 +54,6 @@ namespace Event {
struct { struct {
Event<PHLLS> opened; Event<PHLLS> opened;
Event<PHLLS> closed; Event<PHLLS> closed;
Event<PHLLS> updateRules;
} layer; } layer;
struct { struct {

View file

@ -297,23 +297,31 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {
return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt));
} }
uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) {
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case.
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2;
default: return GL_RGBA;
}
UNREACHABLE();
return GL_RGBA;
}
uint32_t NFormatUtils::glFormatToType(uint32_t gl) {
return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE;
}
std::string NFormatUtils::drmFormatName(DRMFormat drm) { std::string NFormatUtils::drmFormatName(DRMFormat drm) {
auto n = drmGetFormatName(drm); auto n = drmGetFormatName(drm);
if (!n)
return "unknown";
std::string name = n; std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name; return name;
} }
std::string NFormatUtils::drmModifierName(uint64_t mod) { std::string NFormatUtils::drmModifierName(uint64_t mod) {
auto n = drmGetFormatModifierName(mod); auto n = drmGetFormatModifierName(mod);
if (!n)
return "unknown";
std::string name = n; std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name; return name;

View file

@ -53,6 +53,8 @@ namespace NFormatUtils {
bool isFormatOpaque(DRMFormat drm); bool isFormatOpaque(DRMFormat drm);
int pixelsPerBlock(const SPixelFormat* const fmt); int pixelsPerBlock(const SPixelFormat* const fmt);
int minStride(const SPixelFormat* const fmt, int32_t width); int minStride(const SPixelFormat* const fmt, int32_t width);
uint32_t drmFormatToGL(DRMFormat drm);
uint32_t glFormatToType(uint32_t gl);
std::string drmFormatName(DRMFormat drm); std::string drmFormatName(DRMFormat drm);
std::string drmModifierName(uint64_t mod); std::string drmModifierName(uint64_t mod);
DRMFormat alphaFormat(DRMFormat prevFormat); DRMFormat alphaFormat(DRMFormat prevFormat);

View file

@ -30,7 +30,7 @@
#include "../hyprerror/HyprError.hpp" #include "../hyprerror/HyprError.hpp"
#include "../layout/LayoutManager.hpp" #include "../layout/LayoutManager.hpp"
#include "../i18n/Engine.hpp" #include "../i18n/Engine.hpp"
#include "../helpers/cm/ColorManagement.hpp" #include "../protocols/types/ColorManagement.hpp"
#include "sync/SyncTimeline.hpp" #include "sync/SyncTimeline.hpp"
#include "time/Time.hpp" #include "time/Time.hpp"
#include "../desktop/view/LayerSurface.hpp" #include "../desktop/view/LayerSurface.hpp"
@ -82,10 +82,7 @@ void CMonitor::onConnect(bool noRule) {
m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimProgress->setValueAndWarp(0.F);
m_zoomAnimFrameCounter = 0; m_zoomAnimFrameCounter = 0;
g_pEventLoopManager->doLater([] { g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
g_pConfigManager->ensurePersistentWorkspacesPresent();
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
});
m_listeners.frame = m_output->events.frame.listen([this] { m_listeners.frame = m_output->events.frame.listen([this] {
if (m_frameScheduler) if (m_frameScheduler)
@ -293,16 +290,10 @@ void CMonitor::onConnect(bool noRule) {
if (!valid(ws)) if (!valid(ws))
continue; continue;
const auto CURRENTMON = ws->m_monitor.lock(); if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; });
const bool RETURNING = ws->m_lastMonitor == m_name;
const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces
if (RETURNING || RECOVERY) {
g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());
g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
if (RETURNING) ws->m_lastMonitor = "";
ws->m_lastMonitor = "";
} }
} }
@ -438,24 +429,19 @@ void CMonitor::onDisconnect(bool destroy) {
m_enabled = false; m_enabled = false;
m_renderingInitPassed = false; m_renderingInitPassed = false;
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
// Preserve ownership across cascaded monitor disconnects.
// The first disconnected monitor "owns" where a workspace should return.
for (auto const& w : wspToMove) {
if (w && w->m_lastMonitor.empty())
w->m_lastMonitor = m_name;
}
if (BACKUPMON) { if (BACKUPMON) {
// snap cursor // snap cursor
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true); g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
// move workspaces
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
for (auto const& w : wspToMove) { for (auto const& w : wspToMove) {
w->m_lastMonitor = m_name;
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON); g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
} }
@ -933,46 +919,29 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_supportsWideColor = RULE->supportsHDR; m_supportsWideColor = RULE->supportsHDR;
m_supportsHDR = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR;
if (RULE->iccFile.empty()) { m_cmType = RULE->cmType;
// only apply explicit cm settings if we have no icc file switch (m_cmType) {
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
m_cmType = RULE->cmType; case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
switch (m_cmType) { case NCMType::CM_HDR:
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; default: break;
case NCMType::CM_HDR:
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
default: break;
}
m_sdrEotf = RULE->sdrEotf;
m_sdrMinLuminance = RULE->sdrMinLuminance;
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
m_minLuminance = RULE->minLuminance;
m_maxLuminance = RULE->maxLuminance;
m_maxAvgLuminance = RULE->maxAvgLuminance;
applyCMType(m_cmType, m_sdrEotf);
m_sdrSaturation = RULE->sdrSaturation;
m_sdrBrightness = RULE->sdrBrightness;
} else {
auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile);
if (!image) {
Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error());
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
} else {
m_imageDescription = CImageDescription::from(*image);
if (!m_imageDescription) {
Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error());
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
m_imageDescription = CImageDescription::from(SImageDescription{});
}
}
} }
m_sdrEotf = RULE->sdrEotf;
m_sdrMinLuminance = RULE->sdrMinLuminance;
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
m_minLuminance = RULE->minLuminance;
m_maxLuminance = RULE->maxLuminance;
m_maxAvgLuminance = RULE->maxAvgLuminance;
applyCMType(m_cmType, m_sdrEotf);
m_sdrSaturation = RULE->sdrSaturation;
m_sdrBrightness = RULE->sdrBrightness;
Vector2D logicalSize = m_pixelSize / m_scale; Vector2D logicalSize = m_pixelSize / m_scale;
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
// invalid scale, will produce fractional pixels. // invalid scale, will produce fractional pixels.
@ -1054,8 +1023,6 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_damage.setSize(m_transformedSize); m_damage.setSize(m_transformedSize);
updateVCGTRamps();
// Set scale for all surfaces on this monitor, needed for some clients // Set scale for all surfaces on this monitor, needed for some clients
// but not on unsafe state to avoid crashes // but not on unsafe state to avoid crashes
if (!g_pCompositor->m_unsafeState) { if (!g_pCompositor->m_unsafeState) {
@ -1362,7 +1329,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
// move pinned windows // move pinned windows
for (auto const& w : g_pCompositor->m_windows) { for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == POLDWORKSPACE && w->m_pinned) if (w->m_workspace == POLDWORKSPACE && w->m_pinned)
w->layoutTarget()->assignToSpace(pWorkspace->m_space); w->moveToWorkspace(pWorkspace);
} }
if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&
@ -1481,7 +1448,6 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) { if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
PMWSOWNER->m_activeSpecialWorkspace.reset(); PMWSOWNER->m_activeSpecialWorkspace.reset();
g_layoutManager->recalculateMonitor(PMWSOWNER); g_layoutManager->recalculateMonitor(PMWSOWNER);
g_pHyprRenderer->damageMonitor(PMWSOWNER);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
@ -1579,20 +1545,10 @@ Vector2D CMonitor::middle() {
return m_position + m_size / 2.f; return m_position + m_size / 2.f;
} }
const Mat3x3& CMonitor::getTransformMatrix() {
return m_projMatrix;
}
const Mat3x3& CMonitor::getScaleMatrix() {
return m_projOutputMatrix;
}
void CMonitor::updateMatrix() { void CMonitor::updateMatrix() {
m_projMatrix = Mat3x3::identity(); m_projMatrix = Mat3x3::identity();
if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL) if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL)
m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0); m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0);
m_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
} }
WORKSPACEID CMonitor::activeWorkspaceID() { WORKSPACEID CMonitor::activeWorkspaceID() {
@ -1879,7 +1835,7 @@ uint16_t CMonitor::isDSBlocked(bool full) {
// we can't scanout shm buffers. // we can't scanout shm buffers.
const auto params = PSURFACE->m_current.buffer->dmabuf(); const auto params = PSURFACE->m_current.buffer->dmabuf();
if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) { if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) {
reasons |= DS_BLOCK_DMA; reasons |= DS_BLOCK_DMA;
if (!full) if (!full)
return reasons; return reasons;
@ -2216,8 +2172,8 @@ bool CMonitor::canNoShaderCM() {
const auto SRC_DESC_VALUE = SRC_DESC.value()->value(); const auto SRC_DESC_VALUE = SRC_DESC.value()->value();
if (m_imageDescription->value().icc.present) if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0)
return false; return false; // no ICC support
const auto sdrEOTF = NTransferFunction::fromConfig(); const auto sdrEOTF = NTransferFunction::fromConfig();
// only primaries differ // only primaries differ
@ -2236,71 +2192,6 @@ bool CMonitor::doesNoShaderCM() {
return m_noShaderCTM; return m_noShaderCTM;
} }
static std::vector<uint16_t> resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) {
std::vector<uint16_t> out;
out.resize(gammaSize * 3);
//
auto sample = [&](int c, float x) -> uint16_t {
const float maxX = t.entries - 1;
x = std::clamp(x, 0.F, maxX);
const size_t i0 = (size_t)std::floor(x);
const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1);
const float f = x - sc<float>(i0);
const float v0 = sc<float>(t.ch[c][i0]);
const float v1 = sc<float>(t.ch[c][i1]);
const float v = v0 + ((v1 - v0) * f);
int64_t vi = std::round(v);
vi = std::clamp(vi, sc<int64_t>(0), sc<int64_t>(65535));
return sc<uint16_t>(vi);
};
for (size_t i = 0; i < gammaSize; ++i) {
float x = sc<float>(i) * sc<float>(t.entries - 1) / sc<float>(gammaSize - 1);
const uint16_t r = sample(0, x);
const uint16_t g = sample(1, x);
const uint16_t b = sample(2, x);
out[i * 3 + 0] = r;
out[i * 3 + 1] = g;
out[i * 3 + 2] = b;
}
return out;
}
void CMonitor::updateVCGTRamps() {
auto gammaSize = m_output->getGammaSize();
if (gammaSize <= 10) {
Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output");
return;
}
if (!m_imageDescription->value().icc.vcgt) {
if (m_vcgtRampsSet)
m_output->state->setGammaLut({});
m_vcgtRampsSet = false;
return;
}
// build table
auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize);
m_output->state->setGammaLut(table);
m_vcgtRampsSet = true;
}
bool CMonitor::gammaRampsInUse() {
return m_vcgtRampsSet;
}
CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) {
; ;
} }

View file

@ -16,7 +16,7 @@
#include "math/Math.hpp" #include "math/Math.hpp"
#include "../desktop/reserved/ReservedArea.hpp" #include "../desktop/reserved/ReservedArea.hpp"
#include <optional> #include <optional>
#include "cm/ColorManagement.hpp" #include "../protocols/types/ColorManagement.hpp"
#include "signal/Signal.hpp" #include "signal/Signal.hpp"
#include "DamageRing.hpp" #include "DamageRing.hpp"
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
@ -56,7 +56,6 @@ struct SMonitorRule {
float sdrSaturation = 1.0f; // SDR -> HDR float sdrSaturation = 1.0f; // SDR -> HDR
float sdrBrightness = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR
Desktop::CReservedArea reservedArea; Desktop::CReservedArea reservedArea;
std::string iccFile;
int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable
int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable
@ -127,7 +126,7 @@ class CMonitor {
bool m_scheduledRecalc = false; bool m_scheduledRecalc = false;
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL; wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
float m_xwaylandScale = 1.f; float m_xwaylandScale = 1.f;
Mat3x3 m_projMatrix;
std::optional<Vector2D> m_forceSize; std::optional<Vector2D> m_forceSize;
SP<Aquamarine::SOutputMode> m_currentMode; SP<Aquamarine::SOutputMode> m_currentMode;
SP<Aquamarine::CSwapchain> m_cursorSwapchain; SP<Aquamarine::CSwapchain> m_cursorSwapchain;
@ -303,6 +302,7 @@ class CMonitor {
void setSpecialWorkspace(const WORKSPACEID& id); void setSpecialWorkspace(const WORKSPACEID& id);
void moveTo(const Vector2D& pos); void moveTo(const Vector2D& pos);
Vector2D middle(); Vector2D middle();
void updateMatrix();
WORKSPACEID activeWorkspaceID(); WORKSPACEID activeWorkspaceID();
WORKSPACEID activeSpecialWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID();
CBox logicalBox(); CBox logicalBox();
@ -332,11 +332,6 @@ class CMonitor {
bool wantsHDR(); bool wantsHDR();
bool inHDR(); bool inHDR();
bool gammaRampsInUse();
//
const Mat3x3& getTransformMatrix();
const Mat3x3& getScaleMatrix();
/// Has an active workspace with a real fullscreen window (includes special workspace) /// Has an active workspace with a real fullscreen window (includes special workspace)
bool inFullscreenMode(); bool inFullscreenMode();
@ -358,8 +353,8 @@ class CMonitor {
PHLWINDOWREF m_previousFSWindow; PHLWINDOWREF m_previousFSWindow;
bool m_needsHDRupdate = false; bool m_needsHDRupdate = false;
NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); NColorManagement::PImageDescription m_imageDescription;
bool m_noShaderCTM = false; // sets drm CTM, restore needed bool m_noShaderCTM = false; // sets drm CTM, restore needed
// For the list lookup // For the list lookup
@ -367,19 +362,12 @@ class CMonitor {
return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;
} }
Mat3x3 m_projMatrix;
private: private:
void updateMatrix();
Mat3x3 m_projOutputMatrix;
void setupDefaultWS(const SMonitorRule&); void setupDefaultWS(const SMonitorRule&);
WORKSPACEID findAvailableDefaultWS(); WORKSPACEID findAvailableDefaultWS();
void commitDPMSState(bool state); void commitDPMSState(bool state);
void updateVCGTRamps();
bool m_doneScheduled = false; bool m_doneScheduled = false;
bool m_vcgtRampsSet = false;
std::stack<WORKSPACEID> m_prevWorkSpaces; std::stack<WORKSPACEID> m_prevWorkSpaces;
struct { struct {

View file

@ -26,10 +26,7 @@ std::string NTransferFunction::toString(eTF tf) {
return ""; return "";
} }
eTF NTransferFunction::fromConfig(bool useICC) { eTF NTransferFunction::fromConfig() {
if (useICC)
return TF_SRGB;
static auto PSDREOTF = CConfigValue<Hyprlang::STRING>("render:cm_sdr_eotf"); static auto PSDREOTF = CConfigValue<Hyprlang::STRING>("render:cm_sdr_eotf");
static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF); static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF);
static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); }); static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); });

View file

@ -15,5 +15,5 @@ namespace NTransferFunction {
eTF fromString(const std::string tfName); eTF fromString(const std::string tfName);
std::string toString(eTF tf); std::string toString(eTF tf);
eTF fromConfig(bool useICC = false); eTF fromConfig();
} }

View file

@ -1,193 +0,0 @@
#include "ColorManagement.hpp"
#include "../../macros.hpp"
#include <hyprutils/memory/UniquePtr.hpp>
#include <map>
#include <vector>
using namespace NColorManagement;
namespace NColorManagement {
// expected to be small
static std::vector<UP<const CPrimaries>> knownPrimaries;
static std::vector<UP<const CImageDescription>> knownDescriptions;
static std::map<std::pair<uint, uint>, Hyprgraphics::CMatrix3> primariesConversion;
}
const SPCPRimaries& NColorManagement::getPrimaries(ePrimaries name) {
switch (name) {
case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709;
case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020;
case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M;
case CM_PRIMARIES_PAL: return NColorPrimaries::PAL;
case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC;
case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM;
case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ;
case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3;
case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3;
case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB;
default: return NColorPrimaries::DEFAULT_PRIMARIES;
}
}
CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint32_t primariesId) : m_id(primariesId), m_primaries(primaries) {
m_primaries2XYZ = m_primaries.toXYZ();
}
WP<const CPrimaries> CPrimaries::from(const SPCPRimaries& primaries) {
for (const auto& known : knownPrimaries) {
if (known->value() == primaries)
return known;
}
knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1)));
return knownPrimaries.back();
}
WP<const CPrimaries> CPrimaries::from(const ePrimaries name) {
return from(getPrimaries(name));
}
WP<const CPrimaries> CPrimaries::from(const uint32_t primariesId) {
ASSERT(primariesId <= knownPrimaries.size());
return knownPrimaries[primariesId - 1];
}
const SPCPRimaries& CPrimaries::value() const {
return m_primaries;
}
uint CPrimaries::id() const {
return m_id;
}
const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const {
return m_primaries2XYZ;
}
const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP<const CPrimaries> dst) const {
const auto cacheKey = std::make_pair(m_id, dst->m_id);
if (!primariesConversion.contains(cacheKey))
primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries)));
return primariesConversion[cacheKey];
}
CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint32_t imageDescriptionId) :
m_id(imageDescriptionId), m_imageDescription(imageDescription) {
m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id();
}
PImageDescription CImageDescription::from(const SImageDescription& imageDescription) {
for (const auto& known : knownDescriptions) {
if (known->value() == imageDescription)
return known;
}
knownDescriptions.emplace_back(UP<CImageDescription>(new CImageDescription(imageDescription, knownDescriptions.size() + 1)));
return knownDescriptions.back();
}
PImageDescription CImageDescription::from(const uint32_t imageDescriptionId) {
ASSERT(imageDescriptionId <= knownDescriptions.size());
return knownDescriptions[imageDescriptionId - 1];
}
PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const {
auto desc = m_imageDescription;
desc.luminances = luminances;
return CImageDescription::from(desc);
}
const SImageDescription& CImageDescription::value() const {
return m_imageDescription;
}
uint CImageDescription::id() const {
return m_id;
}
WP<const CPrimaries> CImageDescription::getPrimaries() const {
return CPrimaries::from(m_primariesId);
}
static Mat3x3 diag3(const std::array<float, 3>& s) {
return Mat3x3{std::array<float, 9>{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}};
}
static std::optional<Mat3x3> invertMat3(const Mat3x3& m) {
const auto ARR = m.getMatrix();
const double a = ARR[0], b = ARR[1], c = ARR[2];
const double d = ARR[3], e = ARR[4], f = ARR[5];
const double g = ARR[6], h = ARR[7], i = ARR[8];
const double A = (e * i - f * h);
const double B = -(d * i - f * g);
const double C = (d * h - e * g);
const double D = -(b * i - c * h);
const double E = (a * i - c * g);
const double F = -(a * h - b * g);
const double G = (b * f - c * e);
const double H = -(a * f - c * d);
const double I = (a * e - b * d);
const double det = a * A + b * B + c * C;
if (std::abs(det) < 1e-18)
return std::nullopt;
const double invDet = 1.0 / det;
Mat3x3 inv{std::array<float, 9>{
A * invDet,
D * invDet,
G * invDet, //
B * invDet,
E * invDet,
H * invDet, //
C * invDet,
F * invDet,
I * invDet, //
}};
return inv;
}
static std::array<float, 3> matByVec(const Mat3x3& M, const std::array<float, 3>& v) {
const auto ARR = M.getMatrix();
return {ARR[0] * v[0] + ARR[1] * v[1] + ARR[2] * v[2], ARR[3] * v[0] + ARR[4] * v[1] + ARR[5] * v[2], ARR[6] * v[0] + ARR[7] * v[1] + ARR[8] * v[2]};
}
std::optional<Mat3x3> NColorManagement::rgbToXYZFromPrimaries(SPCPRimaries pr) {
const auto R = Hyprgraphics::xy2xyz(pr.red);
const auto G = Hyprgraphics::xy2xyz(pr.green);
const auto B = Hyprgraphics::xy2xyz(pr.blue);
const auto W = Hyprgraphics::xy2xyz(pr.white);
// P has columns R,G,B
Mat3x3 P{std::array<float, 9>{R.x, G.x, B.x, R.y, G.y, B.y, R.z, G.z, B.z}};
auto invP = invertMat3(P);
if (!invP)
return std::nullopt;
const auto S = matByVec(*invP, {W.x, W.y, W.z});
P.multiply(diag3(S)); // RGB->XYZ
return P;
}
Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW) {
static const Mat3x3 Bradford{std::array<float, 9>{0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f}};
static const Mat3x3 BradfordInv = invertMat3(Bradford).value();
const auto srcXYZ = Hyprgraphics::xy2xyz(srcW);
const auto dstXYZ = Hyprgraphics::xy2xyz(dstW);
const auto srcLMS = matByVec(Bradford, {srcXYZ.x, srcXYZ.y, srcXYZ.z});
const auto dstLMS = matByVec(Bradford, {dstXYZ.x, dstXYZ.y, dstXYZ.z});
const std::array<float, 3> scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]};
Mat3x3 result = BradfordInv;
result.multiply(diag3(scale)).multiply(Bradford);
return result;
}

View file

@ -1,278 +0,0 @@
#include "ColorManagement.hpp"
#include "../math/Math.hpp"
#include <cstddef>
#include <fstream>
#include "../../debug/log/Logger.hpp"
#include "../../render/Texture.hpp"
#include "../../render/Renderer.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
#include <lcms2.h>
using namespace NColorManagement;
static std::vector<uint8_t> readBinary(const std::filesystem::path& file) {
std::ifstream ifs(file, std::ios::binary);
if (!ifs.good())
return {};
ifs.seekg(0, std::ios::end);
size_t len = ifs.tellg();
ifs.seekg(0, std::ios::beg);
if (len <= 0)
return {};
std::vector<uint8_t> buf;
buf.resize(len);
ifs.read(reinterpret_cast<char*>(buf.data()), len);
return buf;
}
static uint16_t bigEndianU16(const uint8_t* p) {
return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]);
}
static uint32_t bigEndianU32(const uint8_t* p) {
return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];
}
static constexpr cmsTagSignature makeSig(char a, char b, char c, char d) {
return sc<cmsTagSignature>(sc<uint32_t>(a) << 24 | sc<uint32_t>(b) << 16 | sc<uint32_t>(c) << 8 | sc<uint32_t>(d));
}
static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't');
//
static std::expected<std::optional<SVCGTTable16>, std::string> readVCGT16(cmsHPROFILE prof) {
if (!cmsIsTag(prof, VCGT_SIG))
return std::nullopt;
cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0);
if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header
return std::unexpected("Malformed vcgt tag");
std::vector<uint8_t> raw(n);
if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n)
return std::unexpected("Malformed vcgt tag");
// raw layout:
// 0 ... 3: 'vcgt'
// 4 ... 7: reserved
// 8 ... 11: gammaType (0 = table)
uint32_t gammaType = bigEndianU32(raw.data() + 8);
if (gammaType != 0)
return std::unexpected("VCGT formula type is not supported by Hyprland");
SVCGTTable16 table;
table.channels = bigEndianU16(raw.data() + 12);
table.entries = bigEndianU16(raw.data() + 14);
table.entrySize = bigEndianU16(raw.data() + 16);
// raw+18: reserved u16
Log::logger->log(Log::DEBUG, "readVCGT16: table has {} channels, {} entries, and entry size of {}", table.channels, table.entries, table.entrySize);
if (table.channels != 3 || table.entrySize != 2 || table.entries == 0)
return std::unexpected("invalid vcgt table size");
size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize;
// VCGT is a piece of shit and some absolute fucking mongoloid idiots
// decided it'd be great to have both 18 and 20
// FUCK YOU
size_t tableOff = 20;
auto readTable = [&] -> void {
for (int c = 0; c < 3; ++c) {
table.ch[c].resize(table.entries);
for (uint16_t i = 0; i < table.entries; ++i) {
const uint8_t* p = raw.data() + tableOff + static_cast<ptrdiff_t>((c * table.entries + i) * 2);
table.ch[c][i] = bigEndianU16(p); // 0 ... 65535
}
}
};
if (raw.size() < tableOff + tableBytes) {
tableOff = 18;
if (raw.size() < tableOff + tableBytes) {
Log::logger->log(Log::ERR, "readVCGT16: table is too short, tag is invalid");
return std::unexpected("table is too short");
}
Log::logger->log(Log::DEBUG, "readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18");
readTable();
} else {
readTable();
// if the table's last entry is suspiciously low, we more than likely read an 18 as a 20.
if (table.ch[0][table.entries - 1] < 30000) {
Log::logger->log(Log::DEBUG, "readVCGT16: table is likely offset 18 not 20, re-reading");
tableOff = 18;
readTable();
}
}
if (table.ch[0][table.entries - 1] < 30000) {
Log::logger->log(Log::ERR, "readVCGT16: table is malformed, last value of a gamma ramp can't be {}", table.ch[0][table.entries - 1]);
return std::unexpected("invalid table values");
}
Log::logger->log(Log::DEBUG, "readVCGT16: red channel: [{}, {}, ... {}, {}]", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]);
Log::logger->log(Log::DEBUG, "readVCGT16: green channel: [{}, {}, ... {}, {}]", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]);
Log::logger->log(Log::DEBUG, "readVCGT16: blue channel: [{}, {}, ... {}, {}]", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]);
return table;
}
struct CmsProfileDeleter {
void operator()(cmsHPROFILE p) const {
if (p)
cmsCloseProfile(p);
}
};
struct CmsTransformDeleter {
void operator()(cmsHTRANSFORM t) const {
if (t)
cmsDeleteTransform(t);
}
};
using UniqueProfile = std::unique_ptr<std::remove_pointer_t<cmsHPROFILE>, CmsProfileDeleter>;
using UniqueTransform = std::unique_ptr<std::remove_pointer_t<cmsHTRANSFORM>, CmsTransformDeleter>;
static UniqueProfile createLinearSRGBProfile() {
cmsCIExyYTRIPLE prim{};
// sRGB / Rec.709 primaries
prim.Red.x = 0.6400;
prim.Red.y = 0.3300;
prim.Red.Y = 1.0;
prim.Green.x = 0.3000;
prim.Green.y = 0.6000;
prim.Green.Y = 1.0;
prim.Blue.x = 0.1500;
prim.Blue.y = 0.0600;
prim.Blue.Y = 1.0;
cmsCIExyY wp{};
wp.x = 0.3127;
wp.y = 0.3290;
wp.Y = 1.0; // D65
cmsToneCurve* lin = cmsBuildGamma(nullptr, 1.0);
cmsToneCurve* curves[3] = {lin, lin, lin};
cmsHPROFILE p = cmsCreateRGBProfile(&wp, &prim, curves);
cmsFreeToneCurve(lin);
return UniqueProfile{p};
}
static std::expected<void, std::string> buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) {
UniqueProfile src = createLinearSRGBProfile();
if (!src)
return std::unexpected("Failed to create linear sRGB profile");
// Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe.
const int intent = INTENT_RELATIVE_COLORIMETRIC;
const cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS
// float->float transform (linear input, encoded output in dst device space)
UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)};
if (!xform)
return std::unexpected("Failed to create ICC transform");
Log::logger->log(Log::DEBUG, "Building a {}³ 3D LUT", image.icc.lutSize);
image.icc.present = true;
image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3);
auto idx = [&image](int r, int g, int b) -> size_t {
//
return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3;
};
for (size_t bz = 0; bz < image.icc.lutSize; ++bz) {
for (size_t gy = 0; gy < image.icc.lutSize; ++gy) {
for (size_t rx = 0; rx < image.icc.lutSize; ++rx) {
float in[3] = {
rx / float(image.icc.lutSize - 1),
gy / float(image.icc.lutSize - 1),
bz / float(image.icc.lutSize - 1),
};
float outRGB[3];
cmsDoTransform(xform.get(), in, outRGB, 1);
outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F);
outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F);
outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F);
const size_t o = idx(rx, gy, bz);
image.icc.lutDataPacked[o + 0] = outRGB[0];
image.icc.lutDataPacked[o + 1] = outRGB[1];
image.icc.lutDataPacked[o + 2] = outRGB[2];
}
}
}
Log::logger->log(Log::DEBUG, "3D LUT constructed, size {}", image.icc.lutDataPacked.size());
// upload
image.icc.lutTexture = g_pHyprRenderer->createTexture(image.icc.lutDataPacked, image.icc.lutSize);
return {};
}
std::expected<SImageDescription, std::string> SImageDescription::fromICC(const std::filesystem::path& file) {
static auto PVCGTENABLED = CConfigValue<Hyprlang::INT>("render:icc_vcgt_enabled");
std::error_code ec;
if (!std::filesystem::exists(file, ec) || ec)
return std::unexpected("Invalid file");
SImageDescription image;
image.rawICC = readBinary(file);
if (image.rawICC.empty())
return std::unexpected("Failed to read file");
cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), "r");
if (!prof)
return std::unexpected("CMS failed to open icc file");
CScopeGuard x([&prof] { cmsCloseProfile(prof); });
// only handle RGB (typical display profiles)
if (cmsGetColorSpace(prof) != cmsSigRgbData)
return std::unexpected("Only RGB display profiles are supported");
Log::logger->log(Log::DEBUG, "============= Begin ICC load =============");
Log::logger->log(Log::DEBUG, "ICC size: {} bytes", image.rawICC.size());
if (const auto RET = buildIcc3DLut(prof, image); !RET)
return std::unexpected(RET.error());
if (*PVCGTENABLED) {
auto vcgtRes = readVCGT16(prof);
if (!vcgtRes)
return std::unexpected(vcgtRes.error());
image.icc.vcgt = *vcgtRes;
if (!*vcgtRes)
Log::logger->log(Log::DEBUG, "ICC profile has no VCGT data");
} else
Log::logger->log(Log::DEBUG, "Skipping VCGT load, disabled by config");
Log::logger->log(Log::DEBUG, "============= End ICC load =============");
return image;
}

View file

@ -30,6 +30,8 @@ CHyprError::CHyprError() {
if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged)
g_pHyprRenderer->damageBox(m_damageBox); g_pHyprRenderer->damageBox(m_damageBox);
}); });
m_texture = makeShared<CTexture>();
} }
void CHyprError::queueCreate(std::string message, const CHyprColor& color) { void CHyprError::queueCreate(std::string message, const CHyprColor& color) {
@ -38,8 +40,8 @@ void CHyprError::queueCreate(std::string message, const CHyprColor& color) {
} }
void CHyprError::createQueued() { void CHyprError::createQueued() {
if (m_isCreated && m_texture) if (m_isCreated)
m_texture.reset(); m_texture->destroyTexture();
m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn"));
@ -143,13 +145,12 @@ void CHyprError::createQueued() {
// copy the data to an OpenGL texture we have // copy the data to an OpenGL texture we have
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
auto tex = texture(); m_texture->allocate();
tex->allocate(PMONITOR->m_pixelSize); m_texture->bind();
tex->bind(); m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
@ -186,8 +187,7 @@ void CHyprError::draw() {
if (!m_fadeOpacity->isBeingAnimated()) { if (!m_fadeOpacity->isBeingAnimated()) {
if (m_fadeOpacity->value() == 0.f) { if (m_fadeOpacity->value() == 0.f) {
m_queuedDestroy = false; m_queuedDestroy = false;
if (m_texture) m_texture->destroyTexture();
m_texture.reset();
m_isCreated = false; m_isCreated = false;
m_queued = ""; m_queued = "";
@ -218,7 +218,7 @@ void CHyprError::draw() {
m_monitorChanged = false; m_monitorChanged = false;
CTexPassElement::SRenderData data; CTexPassElement::SRenderData data;
data.tex = texture(); data.tex = m_texture;
data.box = monbox; data.box = monbox;
data.a = m_fadeOpacity->value(); data.a = m_fadeOpacity->value();
@ -239,9 +239,3 @@ bool CHyprError::active() {
float CHyprError::height() { float CHyprError::height() {
return m_lastHeight; return m_lastHeight;
} }
SP<ITexture> CHyprError::texture() {
if (!m_texture)
m_texture = g_pHyprRenderer->createTexture();
return m_texture;
}

View file

@ -18,16 +18,13 @@ class CHyprError {
bool active(); bool active();
float height(); // logical float height(); // logical
//
SP<ITexture> texture();
private: private:
void createQueued(); void createQueued();
std::string m_queued = ""; std::string m_queued = "";
CHyprColor m_queuedColor; CHyprColor m_queuedColor;
bool m_queuedDestroy = false; bool m_queuedDestroy = false;
bool m_isCreated = false; bool m_isCreated = false;
SP<ITexture> m_texture; SP<CTexture> m_texture;
PHLANIMVAR<float> m_fadeOpacity; PHLANIMVAR<float> m_fadeOpacity;
CBox m_damageBox = {0, 0, 0, 0}; CBox m_damageBox = {0, 0, 0, 0};
float m_lastHeight = 0.F; float m_lastHeight = 0.F;

View file

@ -1605,7 +1605,6 @@ I18n::CI18nEngine::CI18nEngine() {
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Ứng dụng <b>{app}</b> đang yêu cầu một quyền không xác định."); huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Ứng dụng <b>{app}</b> đang yêu cầu một quyền không xác định.");
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Ứng dụng <b>{app}</b> đang cố gắng ghi hình màn hình của bạn.\n\nBạn muốn cho phép không?"); huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Ứng dụng <b>{app}</b> đang cố gắng ghi hình màn hình của bạn.\n\nBạn muốn cho phép không?");
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "Ứng dụng <b>{app}</b> đang cố gắng đọc vị trí chuột của bạn.\n\nBạn muốn cho phép không?");
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Ứng dụng <b>{app}</b> đang cố gắng tải plugin: <b>{plugin}</b>.\n\nBạn muốn cho phép không?"); huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Ứng dụng <b>{app}</b> đang cố gắng tải plugin: <b>{plugin}</b>.\n\nBạn muốn cho phép không?");
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Phát hiện bàn phím mới: <b>{keyboard}</b>.\n\nBạn muốn cho phép bàn phím này hoạt động không?"); huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Phát hiện bàn phím mới: <b>{keyboard}</b>.\n\nBạn muốn cho phép bàn phím này hoạt động không?");
huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(không xác định)"); huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(không xác định)");

View file

@ -11,7 +11,13 @@
using namespace Layout; using namespace Layout;
CLayoutManager::CLayoutManager() = default; CLayoutManager::CLayoutManager() {
static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([] {
for (const auto& ws : g_pCompositor->getWorkspaces()) {
ws->m_space->recheckWorkArea();
}
});
}
void CLayoutManager::newTarget(SP<ITarget> target, SP<CSpace> space) { void CLayoutManager::newTarget(SP<ITarget> target, SP<CSpace> space) {
// on a new target: remember desired pos for float, if available // on a new target: remember desired pos for float, if available
@ -61,13 +67,6 @@ void CLayoutManager::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectC
target->space()->resizeTarget(Δ, target, corner); target->space()->resizeTarget(Δ, target, corner);
} }
void CLayoutManager::setTargetGeom(const CBox& box, SP<ITarget> target) {
if (!target->floating())
return;
target->space()->setTargetGeom(box, target);
}
std::expected<void, std::string> CLayoutManager::layoutMsg(const std::string_view& sv) { std::expected<void, std::string> CLayoutManager::layoutMsg(const std::string_view& sv) {
const auto MONITOR = Desktop::focusState()->monitor(); const auto MONITOR = Desktop::focusState()->monitor();

View file

@ -53,7 +53,6 @@ namespace Layout {
void moveMouse(const Vector2D& mousePos); void moveMouse(const Vector2D& mousePos);
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE); void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
void moveTarget(const Vector2D& Δ, SP<ITarget> target); void moveTarget(const Vector2D& Δ, SP<ITarget> target);
void setTargetGeom(const CBox& box, SP<ITarget> target); // floats only
void endDragTarget(); void endDragTarget();
std::expected<void, std::string> layoutMsg(const std::string_view& sv); std::expected<void, std::string> layoutMsg(const std::string_view& sv);

View file

@ -42,16 +42,16 @@ void CAlgorithm::removeTarget(SP<ITarget> target) {
const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target); const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target);
if (IS_FLOATING) { if (IS_FLOATING) {
std::erase(m_floatingTargets, target);
m_floating->removeTarget(target); m_floating->removeTarget(target);
std::erase(m_floatingTargets, target);
return; return;
} }
const bool IS_TILED = std::ranges::contains(m_tiledTargets, target); const bool IS_TILED = std::ranges::contains(m_tiledTargets, target);
if (IS_TILED) { if (IS_TILED) {
std::erase(m_tiledTargets, target);
m_tiled->removeTarget(target); m_tiled->removeTarget(target);
std::erase(m_tiledTargets, target);
return; return;
} }
@ -262,10 +262,3 @@ SP<ITarget> CAlgorithm::getNextCandidate(SP<ITarget> old) {
// god damn it, maybe empty? // god damn it, maybe empty?
return nullptr; return nullptr;
} }
void CAlgorithm::setTargetGeom(const CBox& box, SP<ITarget> target) {
if (!target->floating() || !std::ranges::contains(m_floatingTargets, target))
return;
m_floating->setTargetGeom(box, target);
}

View file

@ -40,8 +40,6 @@ namespace Layout {
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE); void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
void moveTarget(const Vector2D& Δ, SP<ITarget> target); void moveTarget(const Vector2D& Δ, SP<ITarget> target);
void setTargetGeom(const CBox& box, SP<ITarget> target); // only for float
void updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo); void updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo);
void updateTiledAlgo(UP<ITiledAlgorithm>&& algo); void updateTiledAlgo(UP<ITiledAlgorithm>&& algo);

View file

@ -17,9 +17,6 @@ namespace Layout {
// a target is being moved by a delta // a target is being moved by a delta
virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target) = 0; virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target) = 0;
// a target is moved to a pos x size
virtual void setTargetGeom(const CBox& geom, SP<ITarget> target) = 0;
virtual void recenter(SP<ITarget> t); virtual void recenter(SP<ITarget> t);
virtual void recalculate(); virtual void recalculate();

View file

@ -1,10 +1,5 @@
#include "ModeAlgorithm.hpp" #include "ModeAlgorithm.hpp"
#include "../space/Space.hpp"
#include "Algorithm.hpp"
#include "../../helpers/Monitor.hpp"
#include "../../desktop/view/Window.hpp"
using namespace Layout; using namespace Layout;
std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_view& sv) { std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_view& sv) {
@ -14,20 +9,3 @@ std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_vie
std::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() { std::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() {
return std::nullopt; return std::nullopt;
} }
std::optional<Vector2D> IModeAlgorithm::focalPointForDir(SP<ITarget> t, Math::eDirection dir) {
Vector2D focalPoint;
const auto WINDOWIDEALBB =
t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved();
switch (dir) {
case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;
case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;
case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;
case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;
default: return std::nullopt;
}
return focalPoint;
}

View file

@ -44,9 +44,6 @@ namespace Layout {
// optional: predict new window's size // optional: predict new window's size
virtual std::optional<Vector2D> predictSizeForNewTarget(); virtual std::optional<Vector2D> predictSizeForNewTarget();
// Impl'd here: focal point for dir
virtual std::optional<Vector2D> focalPointForDir(SP<ITarget> t, Math::eDirection dir);
protected: protected:
IModeAlgorithm() = default; IModeAlgorithm() = default;

View file

@ -116,8 +116,6 @@ void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) {
PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize;
} }
} }
updateTarget(target);
} }
void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) { void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
@ -154,8 +152,6 @@ void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Ve
// put around the current center, fit in workArea // put around the current center, fit in workArea
target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target)); target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target));
} }
updateTarget(target);
} }
CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) { CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) {
@ -177,7 +173,6 @@ CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t)
void CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) { void CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) {
target->rememberFloatingSize(target->position().size()); target->rememberFloatingSize(target->position().size());
m_datas.erase(target);
} }
void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) { void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
@ -189,8 +184,6 @@ void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> tar
if (g_layoutManager->dragController()->target() == target) if (g_layoutManager->dragController()->target() == target)
target->warpPositionSize(); target->warpPositionSize();
updateTarget(target);
} }
void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) { void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
@ -200,17 +193,12 @@ void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> targe
if (g_layoutManager->dragController()->target() == target) if (g_layoutManager->dragController()->target() == target)
target->warpPositionSize(); target->warpPositionSize();
updateTarget(target);
} }
void CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) { void CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
auto posABackup = a->position(); auto posABackup = a->position();
a->setPositionGlobal(b->position()); a->setPositionGlobal(b->position());
b->setPositionGlobal(posABackup); b->setPositionGlobal(posABackup);
updateTarget(a);
updateTarget(b);
} }
void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) { void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
@ -228,25 +216,4 @@ void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDire
} }
t->setPositionGlobal(pos); t->setPositionGlobal(pos);
updateTarget(t);
}
void CDefaultFloatingAlgorithm::recenter(SP<ITarget> t) {
if (!m_datas.contains(t)) {
IFloatingAlgorithm::recenter(t);
return;
}
t->setPositionGlobal(m_datas.at(t).lastBox);
}
void CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP<ITarget> target) {
target->setPositionGlobal(geom);
updateTarget(target);
}
void CDefaultFloatingAlgorithm::updateTarget(SP<ITarget> t) {
m_datas[t] = {.lastBox = t->position()};
} }

View file

@ -1,7 +1,5 @@
#include "../../FloatingAlgorithm.hpp" #include "../../FloatingAlgorithm.hpp"
#include <map>
namespace Layout { namespace Layout {
class CAlgorithm; class CAlgorithm;
} }
@ -19,22 +17,10 @@ namespace Layout::Floating {
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE); virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target); virtual void moveTarget(const Vector2D& Δ, SP<ITarget> target);
virtual void setTargetGeom(const CBox& geom, SP<ITarget> target);
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b); virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent); virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
virtual void recenter(SP<ITarget> t);
private: private:
CBox fitBoxInWorkArea(const CBox& box, SP<ITarget> t); CBox fitBoxInWorkArea(const CBox& box, SP<ITarget> t);
void updateTarget(SP<ITarget>);
struct SWindowData {
CBox lastBox;
};
std::map<WP<ITarget>, SWindowData> m_datas;
}; };
}; };

View file

@ -99,13 +99,11 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON)) if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
OPENINGON = getClosestNode(MOUSECOORDS); OPENINGON = getClosestNode(MOUSECOORDS);
} else if (*PUSEACTIVE || m_overrideFocalPoint) { } else if (*PUSEACTIVE) {
const auto ACTIVE_WINDOW = Desktop::focusState()->window(); const auto ACTIVE_WINDOW = Desktop::focusState()->window();
if (m_overrideFocalPoint) if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE &&
OPENINGON = getClosestNode(*m_overrideFocalPoint); ACTIVE_WINDOW->m_isMapped)
else if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE &&
ACTIVE_WINDOW->m_isMapped)
OPENINGON = getNodeFromWindow(ACTIVE_WINDOW); OPENINGON = getNodeFromWindow(ACTIVE_WINDOW);
if (!OPENINGON) if (!OPENINGON)
@ -184,7 +182,7 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
// whether or not the override persists after opening one window // whether or not the override persists after opening one window
if (*PERMANENTDIRECTIONOVERRIDE == 0) if (*PERMANENTDIRECTIONOVERRIDE == 0)
m_overrideDirection = Math::DIRECTION_DEFAULT; m_overrideDirection = Math::DIRECTION_DEFAULT;
} else if (*PSMARTSPLIT == 1 || m_overrideFocalPoint) { } else if (*PSMARTSPLIT == 1) {
const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2; const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;
const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w; const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;
const auto DELTA = MOUSECOORDS - PARENT_CENTER; const auto DELTA = MOUSECOORDS - PARENT_CENTER;
@ -216,7 +214,10 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
} }
} }
} else if (*PFORCESPLIT == 0 || !newTarget) { } else if (*PFORCESPLIT == 0 || !newTarget) {
if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) { if ((SIDEBYSIDE &&
VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) ||
(!SIDEBYSIDE &&
VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) {
// we are hovering over the first node, make PNODE first. // we are hovering over the first node, make PNODE first.
NEWPARENT->children[1] = OPENINGON; NEWPARENT->children[1] = OPENINGON;
NEWPARENT->children[0] = PNODE; NEWPARENT->children[0] = PNODE;
@ -241,10 +242,11 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
// and update the previous parent if it exists // and update the previous parent if it exists
if (OPENINGON->pParent) { if (OPENINGON->pParent) {
if (OPENINGON->pParent->children[0] == OPENINGON) if (OPENINGON->pParent->children[0] == OPENINGON) {
OPENINGON->pParent->children[0] = NEWPARENT; OPENINGON->pParent->children[0] = NEWPARENT;
else } else {
OPENINGON->pParent->children[1] = NEWPARENT; OPENINGON->pParent->children[1] = NEWPARENT;
}
} }
// Update the children // Update the children
@ -549,35 +551,41 @@ std::optional<Vector2D> CDwindleAlgorithm::predictSizeForNewTarget() {
} }
void CDwindleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) { void CDwindleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
const auto PNODE = getNodeFromTarget(t); const auto PNODE = getNodeFromTarget(t);
const Vector2D originalPos = t->position().middle(); const Vector2D originalPos = t->position().middle();
if (!PNODE || !t->window()) if (!PNODE || !t->window())
return; return;
const auto FOCAL_POINT = focalPointForDir(t, dir); Vector2D focalPoint;
const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle())); const auto WINDOWIDEALBB =
t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved();
if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK) switch (dir) {
return; // noop case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;
case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;
case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;
case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;
default: return;
}
t->window()->setAnimationsToMove(); t->window()->setAnimationsToMove();
removeTarget(t); removeTarget(t);
const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint);
if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) { if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) {
// move with a focal point // move with a focal point
if (PMONITORFOCAL->m_activeWorkspace) if (PMONITORFOCAL->m_activeWorkspace)
t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT); t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space);
return; return;
} }
movedTarget(t, FOCAL_POINT); movedTarget(t, focalPoint);
// restore focus to the previous position // restore focus to the previous position
if (silent) { if (silent) {
@ -657,28 +665,11 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window()); const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window());
if (ARGS[0] == "togglesplit") { if (ARGS[0] == "togglesplit") {
if (CURRENT_NODE) { if (CURRENT_NODE)
if (!toggleSplit(CURRENT_NODE)) toggleSplit(CURRENT_NODE);
return std::unexpected("can't togglesplit in the current workspace");
}
} else if (ARGS[0] == "swapsplit") { } else if (ARGS[0] == "swapsplit") {
if (CURRENT_NODE) { if (CURRENT_NODE)
if (!swapSplit(CURRENT_NODE)) swapSplit(CURRENT_NODE);
return std::unexpected("can't swapsplit in the current workspace");
}
} else if (ARGS[0] == "rotatesplit") {
if (CURRENT_NODE) {
int angle = 90;
if (!ARGS[1].empty()) {
try {
angle = std::stoi(std::string{ARGS[1]});
} catch (const std::exception& e) {
Log::logger->log(Log::WARN, "Invalid angle argument for rotatesplit: {}", ARGS[1]);
return std::unexpected("Invalid angle argument");
}
}
rotateSplit(CURRENT_NODE, angle);
}
} else if (ARGS[0] == "movetoroot") { } else if (ARGS[0] == "movetoroot") {
auto node = CURRENT_NODE; auto node = CURRENT_NODE;
if (!ARGS[1].empty()) { if (!ARGS[1].empty()) {
@ -688,8 +679,7 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
} }
const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable"; const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable";
if (!moveToRoot(node, STABLE)) moveToRoot(node, STABLE);
return std::unexpected("can't movetoroot in the current workspace");
} else if (ARGS[0] == "preselect") { } else if (ARGS[0] == "preselect") {
auto direction = ARGS[1]; auto direction = ARGS[1];
@ -724,102 +714,42 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
break; break;
} }
} }
} else if (ARGS[0] == "splitratio") {
auto ratio = ARGS[1];
bool exact = ARGS[2].starts_with("exact");
if (ratio.empty())
return std::unexpected("splitratio requires an arg");
auto delta = getPlusMinusKeywordResult(std::string{ratio}, 0.F);
if (!CURRENT_NODE || !CURRENT_NODE->pParent)
return std::unexpected("cannot alter split ratio on no / single node");
if (!delta)
return std::unexpected(std::format("failed to parse \"{}\" as a delta", ratio));
const float newRatio = exact ? *delta : CURRENT_NODE->pParent->splitRatio + *delta;
CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F);
CURRENT_NODE->pParent->recalcSizePosRecursive();
} }
return {}; return {};
} }
bool CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) { void CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {
if (!x || !x->pParent) if (!x || !x->pParent)
return false; return;
if (x->pTarget->fullscreenMode() != FSMODE_NONE) if (x->pTarget->fullscreenMode() != FSMODE_NONE)
return false; return;
x->pParent->splitTop = !x->pParent->splitTop; x->pParent->splitTop = !x->pParent->splitTop;
x->pParent->recalcSizePosRecursive(); x->pParent->recalcSizePosRecursive();
return true;
} }
bool CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) { void CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {
if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent) if (x->pTarget->fullscreenMode() != FSMODE_NONE)
return false; return;
std::swap(x->pParent->children[0], x->pParent->children[1]); std::swap(x->pParent->children[0], x->pParent->children[1]);
x->pParent->recalcSizePosRecursive(); x->pParent->recalcSizePosRecursive();
return true;
} }
void CDwindleAlgorithm::rotateSplit(SP<SDwindleNodeData> x, int angle) { void CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
if (!x || !x->pParent) if (!x || !x->pParent)
return; return;
if (x->pTarget->fullscreenMode() != FSMODE_NONE) if (x->pTarget->fullscreenMode() != FSMODE_NONE)
return; return;
// normalize the angle to multiples of 90 degrees
int normalizedAngle = ((sc<int>(angle / 90) % 4) + 4) % 4; // ensures positive modulo
auto pParent = x->pParent;
bool shouldSwap = false;
switch (normalizedAngle) {
case 0: // 0 degrees - no change
break;
case 1:
if (pParent->splitTop)
shouldSwap = true;
pParent->splitTop = !pParent->splitTop;
break;
case 2: shouldSwap = true; break;
case 3:
if (!pParent->splitTop)
shouldSwap = true;
pParent->splitTop = !pParent->splitTop;
break;
default: break; // should never happen
}
if (shouldSwap)
std::swap(pParent->children[0], pParent->children[1]);
pParent->recalcSizePosRecursive();
}
bool CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
if (!x || !x->pParent)
return false;
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
return false;
// already at root // already at root
if (!x->pParent->pParent) if (!x->pParent->pParent)
return false; return;
auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1]; auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1];
@ -840,6 +770,4 @@ bool CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
std::swap(pRoot->children[0], pRoot->children[1]); std::swap(pRoot->children[0], pRoot->children[1]);
pRoot->recalcSizePosRecursive(); pRoot->recalcSizePosRecursive();
return true;
} }

View file

@ -48,10 +48,9 @@ namespace Layout::Tiled {
SP<SDwindleNodeData> getClosestNode(const Vector2D&, SP<ITarget> skip = nullptr); SP<SDwindleNodeData> getClosestNode(const Vector2D&, SP<ITarget> skip = nullptr);
SP<SDwindleNodeData> getMasterNode(); SP<SDwindleNodeData> getMasterNode();
bool toggleSplit(SP<SDwindleNodeData>); void toggleSplit(SP<SDwindleNodeData>);
bool swapSplit(SP<SDwindleNodeData>); void swapSplit(SP<SDwindleNodeData>);
void rotateSplit(SP<SDwindleNodeData>, int angle = 90); void moveToRoot(SP<SDwindleNodeData>, bool stable = true);
bool moveToRoot(SP<SDwindleNodeData>, bool stable = true);
Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT; Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT;
}; };

View file

@ -403,9 +403,7 @@ void CMasterAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
} }
void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) { void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback"); const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);
const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);
if (!t->window()) if (!t->window())
return; return;
@ -426,10 +424,7 @@ void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir
t->window()->setAnimationsToMove(); t->window()->setAnimationsToMove();
if (t->window()->m_workspace != targetWs) { if (t->window()->m_workspace != targetWs) {
if (!*PMONITORFALLBACK) t->assignToSpace(targetWs->m_space);
return; // noop
t->assignToSpace(targetWs->m_space, focalPointForDir(t, dir));
} else if (PWINDOW2) { } else if (PWINDOW2) {
// if same monitor, switch windows // if same monitor, switch windows
g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget()); g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget());
@ -725,8 +720,8 @@ std::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_v
for (auto& nd : m_masterNodesData) { for (auto& nd : m_masterNodesData) {
if (!nd->isMaster) { if (!nd->isMaster) {
const auto& newMaster = nd; const auto newMaster = nd;
newMaster->isMaster = true; newMaster->isMaster = true;
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
@ -761,8 +756,8 @@ std::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_v
for (auto& nd : m_masterNodesData | std::views::reverse) { for (auto& nd : m_masterNodesData | std::views::reverse) {
if (!nd->isMaster) { if (!nd->isMaster) {
const auto& newMaster = nd; const auto newMaster = nd;
newMaster->isMaster = true; newMaster->isMaster = true;
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
@ -961,9 +956,7 @@ void CMasterAlgorithm::calculateWorkspace() {
const auto STACKWINDOWS = WINDOWS - MASTERS; const auto STACKWINDOWS = WINDOWS - MASTERS;
const auto WORKAREA = m_parent->space()->workArea(); const auto WORKAREA = m_parent->space()->workArea();
const auto PMONITOR = m_parent->space()->workspace()->m_monitor; const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
const auto reservedLeft = PMONITOR ? PMONITOR->m_reservedArea.left() : 0; const auto UNRESERVED_WIDTH = WORKAREA.width + PMONITOR->m_reservedArea.left() + PMONITOR->m_reservedArea.right();
const auto reservedRight = PMONITOR ? PMONITOR->m_reservedArea.right() : 0;
const auto UNRESERVED_WIDTH = WORKAREA.width + reservedLeft + reservedRight;
if (orientation == ORIENTATION_CENTER) { if (orientation == ORIENTATION_CENTER) {
if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) if (STACKWINDOWS >= *SLAVECOUNTFORCENTER)
@ -1081,7 +1074,7 @@ void CMasterAlgorithm::calculateWorkspace() {
} }
nd->size = Vector2D(WIDTH, HEIGHT); nd->size = Vector2D(WIDTH, HEIGHT);
nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(reservedLeft, 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY); nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(PMONITOR->m_reservedArea.left(), 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);
nd->pTarget->setPositionGlobal({nd->position, nd->size}); nd->pTarget->setPositionGlobal({nd->position, nd->size});
mastersLeft--; mastersLeft--;
@ -1199,7 +1192,7 @@ void CMasterAlgorithm::calculateWorkspace() {
continue; continue;
if (onRight) { if (onRight) {
nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? reservedLeft : 0); nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? PMONITOR->m_reservedArea.left() : 0);
nextY = nextYR; nextY = nextYR;
heightLeft = heightLeftR; heightLeft = heightLeftR;
slavesLeft = slavesLeftR; slavesLeft = slavesLeftR;
@ -1224,7 +1217,7 @@ void CMasterAlgorithm::calculateWorkspace() {
} }
} }
nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? reservedRight : reservedLeft)) : WIDTH, HEIGHT); nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedArea.right() : PMONITOR->m_reservedArea.left())) : WIDTH, HEIGHT);
nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
nd->pTarget->setPositionGlobal({nd->position, nd->size}); nd->pTarget->setPositionGlobal({nd->position, nd->size});

View file

@ -202,11 +202,6 @@ void CMonocleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
} }
void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) { void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
if (!*PMONITORFALLBACK)
return; // noop
// try to find a monitor in the specified direction, thats the logical thing // try to find a monitor in the specified direction, thats the logical thing
if (!t || !t->space() || !t->space()->workspace()) if (!t || !t->space() || !t->space()->workspace())
return; return;
@ -220,7 +215,7 @@ void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection di
if (t->window()) if (t->window())
t->window()->setAnimationsToMove(); t->window()->setAnimationsToMove();
t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir)); t->assignToSpace(TARGETWS->m_space);
} }
} }

View file

@ -55,14 +55,12 @@ size_t CScrollTapeController::addStrip(float size) {
return m_strips.size() - 1; return m_strips.size() - 1;
} }
void CScrollTapeController::insertStrip(ssize_t afterIndex, float size) { void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
if (afterIndex >= sc<ssize_t>(m_strips.size())) { if (afterIndex >= m_strips.size()) {
addStrip(size); addStrip(size);
return; return;
} }
afterIndex = std::clamp(afterIndex, sc<ssize_t>(-1L), sc<ssize_t>(INT32_MAX));
SStripData newStrip; SStripData newStrip;
newStrip.size = size; newStrip.size = size;
m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip); m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);

View file

@ -40,7 +40,7 @@ namespace Layout::Tiled {
bool isReversed() const; bool isReversed() const;
size_t addStrip(float size = 1.0F); size_t addStrip(float size = 1.0F);
void insertStrip(ssize_t afterIndex, float size = 1.0F); void insertStrip(size_t afterIndex, float size = 1.0F);
void removeStrip(size_t index); void removeStrip(size_t index);
size_t stripCount() const; size_t stripCount() const;
SStripData& getStrip(size_t index); SStripData& getStrip(size_t index);

View file

@ -190,12 +190,10 @@ size_t SColumnData::idx(SP<ITarget> t) {
} }
size_t SColumnData::idxForHeight(float y) { size_t SColumnData::idxForHeight(float y) {
if (targetDatas.empty())
return 0;
for (size_t i = 0; i < targetDatas.size(); ++i) { for (size_t i = 0; i < targetDatas.size(); ++i) {
if (targetDatas[i]->target->position().y < y) if (targetDatas[i]->target->position().y < y)
continue; continue;
return i == 0 ? 0 : i - 1; return i - 1;
} }
return targetDatas.size() - 1; return targetDatas.size() - 1;
} }
@ -247,28 +245,24 @@ void SColumnData::remove(SP<ITarget> t) {
scrollingData->remove(self.lock()); scrollingData->remove(self.lock());
} }
bool SColumnData::up(SP<SScrollingTargetData> w) { void SColumnData::up(SP<SScrollingTargetData> w) {
for (size_t i = 1; i < targetDatas.size(); ++i) { for (size_t i = 1; i < targetDatas.size(); ++i) {
if (targetDatas[i] != w) if (targetDatas[i] != w)
continue; continue;
std::swap(targetDatas[i], targetDatas[i - 1]); std::swap(targetDatas[i], targetDatas[i - 1]);
return true; break;
} }
return false;
} }
bool SColumnData::down(SP<SScrollingTargetData> w) { void SColumnData::down(SP<SScrollingTargetData> w) {
for (size_t i = 0; i < targetDatas.size() - 1; ++i) { for (size_t i = 0; i < targetDatas.size() - 1; ++i) {
if (targetDatas[i] != w) if (targetDatas[i] != w)
continue; continue;
std::swap(targetDatas[i], targetDatas[i + 1]); std::swap(targetDatas[i], targetDatas[i + 1]);
return true; break;
} }
return false;
} }
SP<SScrollingTargetData> SColumnData::next(SP<SScrollingTargetData> w) { SP<SScrollingTargetData> SColumnData::next(SP<SScrollingTargetData> w) {
@ -302,21 +296,23 @@ SScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) {
} }
SP<SColumnData> SScrollingData::add() { SP<SColumnData> SScrollingData::add() {
auto col = columns.emplace_back(makeShared<SColumnData>(self.lock())); static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
col->self = col; auto col = columns.emplace_back(makeShared<SColumnData>(self.lock()));
col->self = col;
size_t stripIdx = controller->addStrip(algorithm->defaultColumnWidth()); size_t stripIdx = controller->addStrip(*PCOLWIDTH);
controller->getStrip(stripIdx).userData = col; controller->getStrip(stripIdx).userData = col;
return col; return col;
} }
SP<SColumnData> SScrollingData::add(int after) { SP<SColumnData> SScrollingData::add(int after) {
auto col = makeShared<SColumnData>(self.lock()); static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
col->self = col; auto col = makeShared<SColumnData>(self.lock());
col->self = col;
columns.insert(columns.begin() + after + 1, col); columns.insert(columns.begin() + after + 1, col);
controller->insertStrip(after, algorithm->defaultColumnWidth()); controller->insertStrip(after, *PCOLWIDTH);
controller->getStrip(after + 1).userData = col; controller->getStrip(after + 1).userData = col;
return col; return col;
@ -470,21 +466,6 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
m_scrollingData = makeShared<SScrollingData>(this); m_scrollingData = makeShared<SScrollingData>(this);
m_scrollingData->self = m_scrollingData; m_scrollingData->self = m_scrollingData;
// Helper to parse explicit_column_widths string
auto parseColumnWidths = [](const std::string& dir) -> std::vector<float> {
auto widthVec = std::vector<float>();
CConstVarList widths(dir, 0, ',');
for (auto& w : widths) {
try {
widthVec.emplace_back(std::clamp(std::stof(std::string{w}), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH));
} catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); }
}
if (widthVec.empty())
widthVec = {0.333, 0.5, 0.667, 1.0}; // default
return widthVec;
};
// Helper to parse direction string // Helper to parse direction string
auto parseDirection = [](const std::string& dir) -> eScrollDirection { auto parseDirection = [](const std::string& dir) -> eScrollDirection {
if (dir == "left") if (dir == "left")
@ -497,11 +478,19 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
return SCROLL_DIR_RIGHT; // default return SCROLL_DIR_RIGHT; // default
}; };
m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseColumnWidths, parseDirection] { m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseDirection] {
static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction"); static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction");
m_config.configuredWidths.clear(); m_config.configuredWidths.clear();
m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS);
CConstVarList widths(*PCONFWIDTHS, 0, ',');
for (auto& w : widths) {
try {
m_config.configuredWidths.emplace_back(std::stof(std::string{w}));
} catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); }
}
if (m_config.configuredWidths.empty())
m_config.configuredWidths = {0.333, 0.5, 0.667, 1.0};
// Update scroll direction // Update scroll direction
m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
@ -534,7 +523,7 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
}); });
// Initialize default widths and direction // Initialize default widths and direction
m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS); m_config.configuredWidths = {0.333, 0.5, 0.667, 1.0};
m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
} }
@ -627,20 +616,16 @@ void CScrollingAlgorithm::removeTarget(SP<ITarget> target) {
if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) { if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) {
// move the view if this is the last column // move the view if this is the last column
const auto USABLE = usableArea(); const auto USABLE = usableArea();
const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); m_scrollingData->controller->adjustOffset(-(USABLE.w * DATA->column->getColumnWidth()));
const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h;
m_scrollingData->controller->adjustOffset(-(usablePrimary * DATA->column->getColumnWidth()));
} }
DATA->column->remove(target); DATA->column->remove(target);
if (!DATA->column) { if (!DATA->column) {
// column got removed, let's ensure we don't leave any cringe extra space // column got removed, let's ensure we don't leave any cringe extra space
const auto USABLE = usableArea(); const auto USABLE = usableArea();
const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - USABLE.w, 1.0));
const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h;
const double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - usablePrimary, 1.0));
m_scrollingData->controller->setOffset(newOffset); m_scrollingData->controller->setOffset(newOffset);
} }
@ -848,129 +833,66 @@ void CScrollingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection
} }
void CScrollingAlgorithm::moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent) { void CScrollingAlgorithm::moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent) {
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback"); const auto DATA = dataFor(t);
const auto DATA = dataFor(t);
if (!DATA) if (!DATA)
return; return;
const auto TAPE_DIR = getDynamicDirection();
const auto CURRENT_COL = DATA->column.lock(); const auto CURRENT_COL = DATA->column.lock();
const auto current_idx = m_scrollingData->idx(CURRENT_COL); const auto current_idx = m_scrollingData->idx(CURRENT_COL);
auto rotateDir = [this](Math::eDirection dir) -> Math::eDirection { if (dir == Math::DIRECTION_LEFT) {
switch (m_scrollingData->controller->getDirection()) { const auto COL = m_scrollingData->prev(DATA->column.lock());
case SCROLL_DIR_RIGHT: return dir;
case SCROLL_DIR_LEFT: {
if (dir == Math::DIRECTION_LEFT)
return Math::DIRECTION_RIGHT;
if (dir == Math::DIRECTION_RIGHT)
return Math::DIRECTION_LEFT;
return dir;
}
case SCROLL_DIR_UP: {
switch (dir) {
case Math::DIRECTION_UP: return Math::DIRECTION_RIGHT;
case Math::DIRECTION_DOWN: return Math::DIRECTION_LEFT;
case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN;
case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP;
default: break;
}
return dir;
}
case SCROLL_DIR_DOWN: {
switch (dir) {
case Math::DIRECTION_UP: return Math::DIRECTION_LEFT;
case Math::DIRECTION_DOWN: return Math::DIRECTION_RIGHT;
case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN;
case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP;
default: break;
}
return dir;
}
default: break;
}
return dir;
};
const auto ROTATED_DIR = rotateDir(dir);
auto commenceDir = [&]() -> bool {
if (ROTATED_DIR == Math::DIRECTION_LEFT) {
const auto COL = m_scrollingData->prev(DATA->column.lock());
// ignore moves to the origin if we are alone
if (!COL && current_idx == 0 && DATA->column->targetDatas.size() == 1)
return false;
DATA->column->remove(t);
if (!COL) {
const auto NEWCOL = m_scrollingData->add(-1);
NEWCOL->add(DATA);
m_scrollingData->centerOrFitCol(NEWCOL);
} else {
if (COL->targetDatas.size() > 0)
COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
else
COL->add(DATA);
m_scrollingData->centerOrFitCol(COL);
}
return true;
} else if (ROTATED_DIR == Math::DIRECTION_RIGHT) {
const auto COL = m_scrollingData->next(DATA->column.lock());
// ignore move to the right when there is no next column and we're alone
if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && DATA->column->targetDatas.size() == 1)
return false;
DATA->column->remove(t);
if (!COL) {
// make a new one
const auto NEWCOL = m_scrollingData->add();
NEWCOL->add(DATA);
m_scrollingData->centerOrFitCol(NEWCOL);
} else {
if (COL->targetDatas.size() > 0)
COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
else
COL->add(DATA);
m_scrollingData->centerOrFitCol(COL);
}
return true;
} else if (ROTATED_DIR == Math::DIRECTION_UP)
return DATA->column->up(DATA);
else if (ROTATED_DIR == Math::DIRECTION_DOWN)
return DATA->column->down(DATA);
return false;
};
if (!commenceDir()) {
// dir wasn't commenced, move to a workspace if possible
// with the original dir
if (!*PMONITORFALLBACK)
return; // noop
const auto MONINDIR = g_pCompositor->getMonitorInDirection(m_parent->space()->workspace()->m_monitor.lock(), dir);
if (MONINDIR && MONINDIR != m_parent->space()->workspace()->m_monitor && MONINDIR->m_activeWorkspace) {
t->assignToSpace(MONINDIR->m_activeWorkspace->m_space, focalPointForDir(t, dir));
m_scrollingData->recalculate();
// ignore moves to the "origin" when on first column and moving opposite to tape direction
if (!COL && current_idx == 0 && (TAPE_DIR == SCROLL_DIR_RIGHT || TAPE_DIR == SCROLL_DIR_DOWN))
return; return;
DATA->column->remove(t);
if (!COL) {
const auto NEWCOL = m_scrollingData->add(-1);
NEWCOL->add(DATA);
m_scrollingData->centerOrFitCol(NEWCOL);
} else {
if (COL->targetDatas.size() > 0)
COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
else
COL->add(DATA);
m_scrollingData->centerOrFitCol(COL);
} }
} } else if (dir == Math::DIRECTION_RIGHT) {
const auto COL = m_scrollingData->next(DATA->column.lock());
// ignore moves to the "origin" when on last column and moving opposite to tape direction
if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && (TAPE_DIR == SCROLL_DIR_LEFT || TAPE_DIR == SCROLL_DIR_UP))
return;
DATA->column->remove(t);
if (!COL) {
// make a new one
const auto NEWCOL = m_scrollingData->add();
NEWCOL->add(DATA);
m_scrollingData->centerOrFitCol(NEWCOL);
} else {
if (COL->targetDatas.size() > 0)
COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y));
else
COL->add(DATA);
m_scrollingData->centerOrFitCol(COL);
}
} else if (dir == Math::DIRECTION_UP)
DATA->column->up(DATA);
else if (dir == Math::DIRECTION_DOWN)
DATA->column->down(DATA);
m_scrollingData->recalculate(); m_scrollingData->recalculate();
focusTargetUpdate(t); focusTargetUpdate(t);
if (t->window())
g_pCompositor->warpCursorTo(t->window()->middle());
} }
std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::string_view& sv) { std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::string_view& sv) {
@ -1255,9 +1177,8 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
m_scrollingData->recalculate(); m_scrollingData->recalculate();
} }
} else if (ARGS[0] == "focus") { } else if (ARGS[0] == "focus") {
const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
static const auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback"); static const auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
static const auto PCONFWRAPFOCUS = CConfigValue<Hyprlang::INT>("scrolling:wrap_focus");
if (!TDATA || ARGS[1].empty()) if (!TDATA || ARGS[1].empty())
return std::unexpected("no window to focus"); return std::unexpected("no window to focus");
@ -1313,7 +1234,7 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
return {}; return {};
} else } else
PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front(); PREV = m_scrollingData->columns.back();
} }
auto pTargetData = findBestNeighbor(TDATA, PREV); auto pTargetData = findBestNeighbor(TDATA, PREV);
@ -1335,7 +1256,7 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
return {}; return {};
} else } else
NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back(); NEXT = m_scrollingData->columns.front();
} }
auto pTargetData = findBestNeighbor(TDATA, NEXT); auto pTargetData = findBestNeighbor(TDATA, NEXT);
@ -1362,8 +1283,6 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
m_scrollingData->recalculate(); m_scrollingData->recalculate();
} else if (ARGS[0] == "swapcol") { } else if (ARGS[0] == "swapcol") {
static const auto PCONFWRAPSWAPCOL = CConfigValue<Hyprlang::INT>("scrolling:wrap_swapcol");
if (ARGS.size() < 2) if (ARGS.size() < 2)
return std::unexpected("not enough args"); return std::unexpected("not enough args");
@ -1389,15 +1308,9 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
// wrap around swaps // wrap around swaps
if (direction == "l") if (direction == "l")
if (*PCONFWRAPSWAPCOL == 1) targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);
targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);
else
targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1);
else if (direction == "r") else if (direction == "r")
if (*PCONFWRAPSWAPCOL == 1) targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);
targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);
else
targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1);
else else
return std::unexpected("no target (invalid direction?)"); return std::unexpected("no target (invalid direction?)");
; ;
@ -1505,14 +1418,9 @@ CBox CScrollingAlgorithm::usableArea() {
CBox box = m_parent->space()->workArea(); CBox box = m_parent->space()->workArea();
// doesn't matter, this happens when this algo is about to be destroyed // doesn't matter, this happens when this algo is about to be destroyed
if (!m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor) if (!m_parent->space()->workspace())
return box; return box;
box.translate(-m_parent->space()->workspace()->m_monitor->m_position); box.translate(-m_parent->space()->workspace()->m_monitor->m_position);
return box; return box;
} }
float CScrollingAlgorithm::defaultColumnWidth() {
static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
return std::clamp(*PCOLWIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH);
}

View file

@ -40,8 +40,8 @@ namespace Layout::Tiled {
// index of lowest target that is above y. // index of lowest target that is above y.
size_t idxForHeight(float y); size_t idxForHeight(float y);
bool up(SP<SScrollingTargetData> w); void up(SP<SScrollingTargetData> w);
bool down(SP<SScrollingTargetData> w); void down(SP<SScrollingTargetData> w);
SP<SScrollingTargetData> next(SP<SScrollingTargetData> w); SP<SScrollingTargetData> next(SP<SScrollingTargetData> w);
SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w); SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w);
@ -138,8 +138,6 @@ namespace Layout::Tiled {
void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent); void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
void focusOnInput(SP<ITarget> target, eInputMode input); void focusOnInput(SP<ITarget> target, eInputMode input);
float defaultColumnWidth();
friend struct SScrollingData; friend struct SScrollingData;
}; };
}; };

View file

@ -6,7 +6,6 @@
#include "../../debug/log/Logger.hpp" #include "../../debug/log/Logger.hpp"
#include "../../desktop/Workspace.hpp" #include "../../desktop/Workspace.hpp"
#include "../../config/ConfigManager.hpp" #include "../../config/ConfigManager.hpp"
#include "../../event/EventBus.hpp"
using namespace Layout; using namespace Layout;
@ -18,12 +17,6 @@ SP<CSpace> CSpace::create(PHLWORKSPACE w) {
CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) { CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) {
recheckWorkArea(); recheckWorkArea();
// NOLINTNEXTLINE
m_geomUpdateCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] {
recheckWorkArea();
m_algorithm->recalculate();
});
} }
void CSpace::add(SP<ITarget> t) { void CSpace::add(SP<ITarget> t) {
@ -183,11 +176,6 @@ void CSpace::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool sil
m_algorithm->moveTargetInDirection(t, dir, silent); m_algorithm->moveTargetInDirection(t, dir, silent);
} }
void CSpace::setTargetGeom(const CBox& box, SP<ITarget> target) {
if (m_algorithm)
m_algorithm->setTargetGeom(box, target);
}
SP<ITarget> CSpace::getNextCandidate(SP<ITarget> old) { SP<ITarget> CSpace::getNextCandidate(SP<ITarget> old) {
return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old); return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old);
} }

View file

@ -47,7 +47,6 @@ namespace Layout {
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE); void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
void moveTarget(const Vector2D& Δ, SP<ITarget> target); void moveTarget(const Vector2D& Δ, SP<ITarget> target);
void setTargetGeom(const CBox& box, SP<ITarget> target); // only for float
SP<CAlgorithm> algorithm() const; SP<CAlgorithm> algorithm() const;
@ -64,8 +63,5 @@ namespace Layout {
// work area is in global coords // work area is in global coords
CBox m_workArea, m_floatingWorkArea; CBox m_workArea, m_floatingWorkArea;
// for recalc
CHyprSignalListener m_geomUpdateCallback;
}; };
}; };

View file

@ -239,8 +239,6 @@ void CDragStateController::dragEnd() {
draggingTarget->damageEntire(); draggingTarget->damageEntire();
g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget);
Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
m_wasDraggingWindow = false; m_wasDraggingWindow = false;

View file

@ -10,9 +10,6 @@
#include "../../Compositor.hpp" #include "../../Compositor.hpp"
#include "../../render/Renderer.hpp" #include "../../render/Renderer.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
using namespace Layout; using namespace Layout;
SP<ITarget> CWindowTarget::create(PHLWINDOW w) { SP<ITarget> CWindowTarget::create(PHLWINDOW w) {
@ -37,9 +34,6 @@ void CWindowTarget::setPositionGlobal(const CBox& box) {
void CWindowTarget::updatePos() { void CWindowTarget::updatePos() {
g_pHyprRenderer->damageWindow(m_window.lock());
CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); });
if (!m_space) if (!m_space)
return; return;
@ -61,11 +55,6 @@ void CWindowTarget::updatePos() {
// Tiled is more complicated. // Tiled is more complicated.
// if we are in maximized, force the box to be max work area.
// TODO: this shouldn't be here.
if (fullscreenMode() == FSMODE_MAXIMIZED)
ITarget::setPositionGlobal(m_space->workArea(floating()));
const auto PMONITOR = m_space->workspace()->m_monitor; const auto PMONITOR = m_space->workspace()->m_monitor;
const auto PWORKSPACE = m_space->workspace(); const auto PWORKSPACE = m_space->workspace();
@ -115,7 +104,7 @@ void CWindowTarget::updatePos() {
Vector2D ratioPadding; Vector2D ratioPadding;
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && fullscreenMode() == FSMODE_NONE) { if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1) {
const Vector2D originalSize = MONITOR_WORKAREA.size(); const Vector2D originalSize = MONITOR_WORKAREA.size();
const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y; const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
@ -142,7 +131,7 @@ void CWindowTarget::updatePos() {
calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;
calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
if (isPseudo() && fullscreenMode() == FSMODE_NONE) { if (isPseudo()) {
// Calculate pseudo // Calculate pseudo
float scale = 1; float scale = 1;
@ -173,14 +162,18 @@ void CWindowTarget::updatePos() {
static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled"); static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
if (*PCLAMP_TILED) { if (*PCLAMP_TILED) {
Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); const auto borderSize = m_window->getRealBorderSize();
Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
calcSize = calcSize.clamp(minSize, maxSize);
Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable);
Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} :
m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable);
calcSize = calcSize.clamp(minSize, maxSize);
calcPos += (availableSpace - calcSize) / 2.0; calcPos += (availableSpace - calcSize) / 2.0;
calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x); calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x + borderSize, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x - borderSize);
calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y); calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize);
} }
if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) { if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) {
@ -377,6 +370,5 @@ void CWindowTarget::onUpdateSpace() {
m_window->m_monitor = space()->workspace()->m_monitor; m_window->m_monitor = space()->workspace()->m_monitor;
m_window->moveToWorkspace(space()->workspace()); m_window->moveToWorkspace(space()->workspace());
m_window->updateToplevel(); m_window->updateToplevel();
m_window->updateWindowData();
m_window->updateWindowDecos(); m_window->updateWindowDecos();
} }

View file

@ -109,6 +109,9 @@ CKeybindManager::CKeybindManager() {
m_dispatchers["togglegroup"] = toggleGroup; m_dispatchers["togglegroup"] = toggleGroup;
m_dispatchers["changegroupactive"] = changeGroupActive; m_dispatchers["changegroupactive"] = changeGroupActive;
m_dispatchers["movegroupwindow"] = moveGroupWindow; m_dispatchers["movegroupwindow"] = moveGroupWindow;
m_dispatchers["togglesplit"] = toggleSplit;
m_dispatchers["swapsplit"] = swapSplit;
m_dispatchers["splitratio"] = alterSplitRatio;
m_dispatchers["focusmonitor"] = focusMonitor; m_dispatchers["focusmonitor"] = focusMonitor;
m_dispatchers["movecursortocorner"] = moveCursorToCorner; m_dispatchers["movecursortocorner"] = moveCursorToCorner;
m_dispatchers["movecursor"] = moveCursor; m_dispatchers["movecursor"] = moveCursor;
@ -1465,10 +1468,9 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) {
} }
SDispatchResult CKeybindManager::moveFocusTo(std::string args) { SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen"); static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen");
static auto PGROUPCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_groupfirst"); static auto PGROUPCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_groupfirst");
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback"); Math::eDirection dir = Math::fromChar(args[0]);
Math::eDirection dir = Math::fromChar(args[0]);
if (dir == Math::DIRECTION_DEFAULT) { if (dir == Math::DIRECTION_DEFAULT) {
Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]);
@ -1477,8 +1479,7 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) {
if (*PMONITORFALLBACK) tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));
tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));
return {}; return {};
} }
@ -1508,7 +1509,7 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir)); Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir));
if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir))) if (tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)))
return {}; return {};
static auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback"); static auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
@ -1689,10 +1690,7 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) {
// index starts from '1'; '0' means last window // index starts from '1'; '0' means last window
try { try {
const int INDEX = std::stoi(args); const int INDEX = std::stoi(args);
if (INDEX <= 0) PWINDOW->m_group->setCurrent(INDEX);
PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1);
else
PWINDOW->m_group->setCurrent(INDEX - 1);
} catch (...) { return {.success = false, .error = "invalid idx"}; } } catch (...) { return {.success = false, .error = "invalid idx"}; }
return {}; return {};
@ -1706,6 +1704,18 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) {
return {}; return {};
} }
SDispatchResult CKeybindManager::toggleSplit(std::string args) {
return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::swapSplit(std::string args) {
return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::alterSplitRatio(std::string args) {
return {.success = false, .error = "removed - use layoutmsg"};
}
SDispatchResult CKeybindManager::focusMonitor(std::string arg) { SDispatchResult CKeybindManager::focusMonitor(std::string arg) {
const auto PMONITOR = g_pCompositor->getMonitorFromString(arg); const auto PMONITOR = g_pCompositor->getMonitorFromString(arg);
tryMoveFocusToMonitor(PMONITOR); tryMoveFocusToMonitor(PMONITOR);
@ -2737,9 +2747,7 @@ void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string&
WP<Desktop::View::CGroup> group = pWindow->m_group; WP<Desktop::View::CGroup> group = pWindow->m_group;
const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT; pWindow->m_group->remove(pWindow);
pWindow->m_group->remove(pWindow, direction);
if (*BFOCUSREMOVEDWINDOW || !group) { if (*BFOCUSREMOVEDWINDOW || !group) {
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);

View file

@ -194,7 +194,10 @@ class CKeybindManager {
static SDispatchResult swapActive(std::string); static SDispatchResult swapActive(std::string);
static SDispatchResult toggleGroup(std::string); static SDispatchResult toggleGroup(std::string);
static SDispatchResult changeGroupActive(std::string); static SDispatchResult changeGroupActive(std::string);
static SDispatchResult alterSplitRatio(std::string);
static SDispatchResult focusMonitor(std::string); static SDispatchResult focusMonitor(std::string);
static SDispatchResult toggleSplit(std::string);
static SDispatchResult swapSplit(std::string);
static SDispatchResult moveCursorToCorner(std::string); static SDispatchResult moveCursorToCorner(std::string);
static SDispatchResult moveCursor(std::string); static SDispatchResult moveCursor(std::string);
static SDispatchResult workspaceOpt(std::string); static SDispatchResult workspaceOpt(std::string);

View file

@ -8,7 +8,6 @@
#include "../protocols/IdleNotify.hpp" #include "../protocols/IdleNotify.hpp"
#include "../protocols/core/Compositor.hpp" #include "../protocols/core/Compositor.hpp"
#include "../protocols/core/Seat.hpp" #include "../protocols/core/Seat.hpp"
#include "debug/log/Logger.hpp"
#include "eventLoop/EventLoopManager.hpp" #include "eventLoop/EventLoopManager.hpp"
#include "../render/pass/TexPassElement.hpp" #include "../render/pass/TexPassElement.hpp"
#include "../managers/input/InputManager.hpp" #include "../managers/input/InputManager.hpp"
@ -19,12 +18,9 @@
#include "../helpers/time/Time.hpp" #include "../helpers/time/Time.hpp"
#include "../helpers/Drm.hpp" #include "../helpers/Drm.hpp"
#include "../event/EventBus.hpp" #include "../event/EventBus.hpp"
#include <climits>
#include <cstring> #include <cstring>
#include <gbm.h> #include <gbm.h>
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <hyprutils/math/Region.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/utils/ScopeGuard.hpp> #include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils; using namespace Hyprutils::Utils;
@ -410,7 +406,7 @@ bool CPointerManager::setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquam
return true; return true;
} }
SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager::SMonitorPointerState> state, SP<ITexture> texture) { SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
auto maxSize = state->monitor->m_output->cursorPlaneSize(); auto maxSize = state->monitor->m_output->cursorPlaneSize();
auto const& cursorSize = m_currentCursorImage.size; auto const& cursorSize = m_currentCursorImage.size;
@ -543,23 +539,24 @@ SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager
// we need to scale the cursor to the right size, because it might not be (esp with XCursor) // we need to scale the cursor to the right size, because it might not be (esp with XCursor)
const auto SCALE = texture->m_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale); const auto SCALE = texture->m_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale);
const auto SX = SCALE.x, SY = SCALE.y; cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.y);
const auto BW = sc<double>(DMABUF.size.x), BH = sc<double>(DMABUF.size.y);
// Cairo pattern matrix maps destination coords to source coords (inverse of visual transform). if (TR) {
// x_src = xx * x_dst + xy * y_dst + x0 cairo_matrix_rotate(&matrixPre, M_PI_2 * sc<double>(TR));
// y_src = yx * x_dst + yy * y_dst + y0
// cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0) // FIXME: this is wrong, and doesn't work for 5, 6 and 7. (flipped + rot)
switch (TR) { // cba to do it rn, does anyone fucking use that??
case WL_OUTPUT_TRANSFORM_NORMAL: if (TR >= WL_OUTPUT_TRANSFORM_FLIPPED) {
default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break; cairo_matrix_scale(&matrixPre, -1, 1);
case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break; cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0);
case WL_OUTPUT_TRANSFORM_180: cairo_matrix_init(&matrixPre, -SX, 0, 0, -SY, SX * BW, SY * BH); break; }
case WL_OUTPUT_TRANSFORM_270: cairo_matrix_init(&matrixPre, 0, -SY, SX, 0, 0, SY * BH); break;
case WL_OUTPUT_TRANSFORM_FLIPPED: cairo_matrix_init(&matrixPre, -SX, 0, 0, SY, SX * BW, 0); break; if (TR == 3 || TR == 7)
case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break; cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0);
case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break; else if (TR == 2 || TR == 6)
case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break; cairo_matrix_translate(&matrixPre, -DMABUF.size.x, -DMABUF.size.y);
else if (TR == 1 || TR == 5)
cairo_matrix_translate(&matrixPre, 0, -DMABUF.size.y);
} }
cairo_pattern_set_matrix(PATTERNPRE, &matrixPre); cairo_pattern_set_matrix(PATTERNPRE, &matrixPre);
@ -592,14 +589,15 @@ SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager
RBO->bind(); RBO->bind();
g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, INT_MAX, INT_MAX}, RBO); const auto& damageSize = state->monitor->m_output->cursorPlaneSize();
g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, damageSize.x, damageSize.y}, RBO);
g_pHyprOpenGL->clear(CHyprColor{0.F, 0.F, 0.F, 0.F}); // ensure the RBO is zero initialized. g_pHyprOpenGL->clear(CHyprColor{0.F, 0.F, 0.F, 0.F}); // ensure the RBO is zero initialized.
CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()}; CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()};
Log::logger->log(Log::TRACE, "[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}", state->monitor->m_name, m_currentCursorImage.size, Log::logger->log(Log::TRACE, "[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}", state->monitor->m_name, m_currentCursorImage.size,
cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size()); cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size());
g_pHyprOpenGL->renderTexture(texture, xbox, {.noCM = true}); g_pHyprOpenGL->renderTexture(texture, xbox, {});
g_pHyprOpenGL->end(); g_pHyprOpenGL->end();
g_pHyprOpenGL->m_renderData.pMonitor.reset(); g_pHyprOpenGL->m_renderData.pMonitor.reset();
@ -903,13 +901,13 @@ const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() {
return m_currentCursorImage; return m_currentCursorImage;
} }
SP<ITexture> CPointerManager::getCurrentCursorTexture() { SP<CTexture> CPointerManager::getCurrentCursorTexture() {
if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture)) if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture))
return nullptr; return nullptr;
if (m_currentCursorImage.pBuffer) { if (m_currentCursorImage.pBuffer) {
if (!m_currentCursorImage.bufferTex) if (!m_currentCursorImage.bufferTex)
m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true); m_currentCursorImage.bufferTex = makeShared<CTexture>(m_currentCursorImage.pBuffer, true);
return m_currentCursorImage.bufferTex; return m_currentCursorImage.bufferTex;
} }

View file

@ -12,7 +12,7 @@
class CMonitor; class CMonitor;
class IHID; class IHID;
class ITexture; class CTexture;
AQUAMARINE_FORWARD(IBuffer); AQUAMARINE_FORWARD(IBuffer);
@ -71,7 +71,7 @@ class CPointerManager {
struct SCursorImage { struct SCursorImage {
SP<Aquamarine::IBuffer> pBuffer; SP<Aquamarine::IBuffer> pBuffer;
SP<ITexture> bufferTex; SP<CTexture> bufferTex;
WP<Desktop::View::CWLSurface> surface; WP<Desktop::View::CWLSurface> surface;
Vector2D hotspot; Vector2D hotspot;
@ -83,7 +83,7 @@ class CPointerManager {
}; };
const SCursorImage& currentCursorImage(); const SCursorImage& currentCursorImage();
SP<ITexture> getCurrentCursorTexture(); SP<CTexture> getCurrentCursorTexture();
struct { struct {
CSignalT<> cursorChanged; CSignalT<> cursorChanged;
@ -181,7 +181,7 @@ class CPointerManager {
std::vector<SP<SMonitorPointerState>> m_monitorStates; std::vector<SP<SMonitorPointerState>> m_monitorStates;
SP<SMonitorPointerState> stateFor(PHLMONITOR mon); SP<SMonitorPointerState> stateFor(PHLMONITOR mon);
bool attemptHardwareCursor(SP<SMonitorPointerState> state); bool attemptHardwareCursor(SP<SMonitorPointerState> state);
SP<Aquamarine::IBuffer> renderHWCursorBuffer(SP<SMonitorPointerState> state, SP<ITexture> texture); SP<Aquamarine::IBuffer> renderHWCursorBuffer(SP<SMonitorPointerState> state, SP<CTexture> texture);
bool setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquamarine::IBuffer> buf); bool setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquamarine::IBuffer> buf);
struct { struct {

View file

@ -847,9 +847,6 @@ void CInputManager::processMouseDownKill(const IPointer::SButtonEvent& e) {
break; break;
} }
g_pEventManager->postEvent(SHyprIPCEvent({.event = "kill", .data = std::format("{:x}", rc<uintptr_t>(PWINDOW.m_data))}));
Event::bus()->m_events.window.kill.emit(PWINDOW);
// kill the mf // kill the mf
kill(PWINDOW->getPID(), SIGKILL); kill(PWINDOW->getPID(), SIGKILL);
break; break;

View file

@ -169,10 +169,10 @@ bool CCursorshareSession::copy() {
return false; return false;
} }
auto outFB = g_pHyprRenderer->createFB(); CFramebuffer outFB;
outFB->alloc(m_bufferSize.x, m_bufferSize.y, m_format); outFB.alloc(m_bufferSize.x, m_bufferSize.y, m_format);
if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB, true)) {
LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm"); LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm");
return false; return false;
} }
@ -182,8 +182,8 @@ bool CCursorshareSession::copy() {
g_pHyprRenderer->endRender(); g_pHyprRenderer->endRender();
g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor; g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor;
outFB->bind(); outFB.bind();
glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID());
glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
@ -212,7 +212,7 @@ bool CCursorshareSession::copy() {
g_pHyprOpenGL->m_renderData.pMonitor.reset(); g_pHyprOpenGL->m_renderData.pMonitor.reset();
m_pendingFrame.buffer->endDataPtr(); m_pendingFrame.buffer->endDataPtr();
GLFB(outFB)->unbind(); outFB.unbind();
glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

View file

@ -10,7 +10,6 @@
#include "../../helpers/Monitor.hpp" #include "../../helpers/Monitor.hpp"
#include "../../desktop/view/Window.hpp" #include "../../desktop/view/Window.hpp"
#include "../../desktop/state/FocusState.hpp" #include "../../desktop/state/FocusState.hpp"
#include <hyprutils/math/Region.hpp>
using namespace Screenshare; using namespace Screenshare;
@ -134,7 +133,7 @@ void CScreenshareFrame::copy() {
// store a snapshot before the permission popup so we don't break screenshots // store a snapshot before the permission popup so we don't break screenshots
const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY); const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY);
if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {
if (!m_session->m_tempFB || !m_session->m_tempFB->isAllocated()) if (!m_session->m_tempFB.isAllocated())
storeTempFB(); storeTempFB();
// don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty // don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty
@ -160,7 +159,7 @@ void CScreenshareFrame::renderMonitor() {
const auto PMONITOR = m_session->monitor(); const auto PMONITOR = m_session->monitor();
auto TEXTURE = g_pHyprRenderer->createTexture(PMONITOR->m_output->state->state().buffer); auto TEXTURE = makeShared<CTexture>(PMONITOR->m_output->state->state().buffer);
const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client); const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client);
g_pHyprOpenGL->m_renderData.transformDamage = false; g_pHyprOpenGL->m_renderData.transformDamage = false;
@ -296,11 +295,8 @@ void CScreenshareFrame::renderWindow() {
return; return;
auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource); auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource);
if (!pointerSurface)
return;
auto box = pointerSurface->getSurfaceBoxGlobal(); if (!pointerSurface || pointerSurface->getSurfaceBoxGlobal()->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())
if (!box.has_value() || box->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())
return; return;
if (Desktop::focusState()->window() != m_session->m_window) if (Desktop::focusState()->window() != m_session->m_window)
@ -326,10 +322,10 @@ void CScreenshareFrame::render() {
return; return;
} }
if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) { if (m_session->m_tempFB.isAllocated()) {
CBox texbox = {{}, m_bufferSize}; CBox texbox = {{}, m_bufferSize};
g_pHyprOpenGL->renderTexture(m_session->m_tempFB->getTexture(), texbox, {}); g_pHyprOpenGL->renderTexture(m_session->m_tempFB.getTexture(), texbox, {});
m_session->m_tempFB->release(); m_session->m_tempFB.release();
return; return;
} }
@ -382,12 +378,12 @@ bool CScreenshareFrame::copyShm() {
return false; return false;
} }
const auto PMONITOR = m_session->monitor(); const auto PMONITOR = m_session->monitor();
auto outFB = g_pHyprRenderer->createFB(); CFramebuffer outFB;
outFB->alloc(m_bufferSize.x, m_bufferSize.y, shm.format); outFB.alloc(m_bufferSize.x, m_bufferSize.y, shm.format);
if (!g_pHyprRenderer->beginRender(PMONITOR, m_damage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { if (!g_pHyprRenderer->beginRender(PMONITOR, m_damage, RENDER_MODE_FULL_FAKE, nullptr, &outFB, true)) {
LOGM(Log::ERR, "Can't copy: failed to begin rendering"); LOGM(Log::ERR, "Can't copy: failed to begin rendering");
return false; return false;
} }
@ -399,8 +395,8 @@ bool CScreenshareFrame::copyShm() {
g_pHyprRenderer->endRender(); g_pHyprRenderer->endRender();
g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR;
outFB->bind(); outFB.bind();
glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID());
glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
@ -442,7 +438,7 @@ bool CScreenshareFrame::copyShm() {
}); });
} }
GLFB(outFB)->unbind(); outFB.unbind();
glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
@ -457,13 +453,13 @@ bool CScreenshareFrame::copyShm() {
} }
void CScreenshareFrame::storeTempFB() { void CScreenshareFrame::storeTempFB() {
if (!m_session->m_tempFB) g_pHyprRenderer->makeEGLCurrent();
m_session->m_tempFB = g_pHyprRenderer->createFB();
m_session->m_tempFB->alloc(m_bufferSize.x, m_bufferSize.y); m_session->m_tempFB.alloc(m_bufferSize.x, m_bufferSize.y);
CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
if (!g_pHyprRenderer->beginRender(m_session->monitor(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, m_session->m_tempFB, true)) { if (!g_pHyprRenderer->beginRender(m_session->monitor(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &m_session->m_tempFB, true)) {
LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb");
return; return;
} }

View file

@ -145,19 +145,15 @@ WP<CScreenshareSession> CScreenshareManager::getManagedSession(eScreenshareType
auto& session = *it; auto& session = *it;
session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() { session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() {
if (!session.expired()) std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return !s || session.expired() || s->m_session == session->m_session; });
std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); });
}); });
return session->m_session; return session->m_session;
} }
bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) { void CScreenshareManager::destroyClientSessions(wl_client* client) {
return std::ranges::any_of(m_sessions, [monitor](const auto& s) { LOGM(Log::TRACE, "Destroy client sessions for {:x}", (uintptr_t)client);
if (!s) std::erase_if(m_managedSessions, [&](const auto& session) { return !session || session->m_session->m_client == client; });
return false;
return (s->m_type == SHARE_MONITOR || s->m_type == SHARE_REGION) && s->m_monitor == monitor;
});
} }
CScreenshareManager::SManagedSession::SManagedSession(UP<CScreenshareSession>&& session) : m_session(std::move(session)) { CScreenshareManager::SManagedSession::SManagedSession(UP<CScreenshareSession>&& session) : m_session(std::move(session)) {

View file

@ -75,7 +75,7 @@ namespace Screenshare {
std::vector<DRMFormat> m_formats; std::vector<DRMFormat> m_formats;
Vector2D m_bufferSize = Vector2D(0, 0); Vector2D m_bufferSize = Vector2D(0, 0);
SP<IFramebuffer> m_tempFB; CFramebuffer m_tempFB;
SP<CEventLoopTimer> m_shareStopTimer; SP<CEventLoopTimer> m_shareStopTimer;
bool m_sharing = false; bool m_sharing = false;
@ -206,8 +206,9 @@ namespace Screenshare {
UP<CCursorshareSession> newCursorSession(wl_client* client, WP<CWLPointerResource> pointer); UP<CCursorshareSession> newCursorSession(wl_client* client, WP<CWLPointerResource> pointer);
void destroyClientSessions(wl_client* client);
void onOutputCommit(PHLMONITOR monitor); void onOutputCommit(PHLMONITOR monitor);
bool isOutputBeingSSd(PHLMONITOR monitor);
private: private:
std::vector<WP<CScreenshareSession>> m_sessions; std::vector<WP<CScreenshareSession>> m_sessions;

View file

@ -39,8 +39,7 @@ CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion,
CScreenshareSession::~CScreenshareSession() { CScreenshareSession::~CScreenshareSession() {
stop(); stop();
uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get()); LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}", m_type, m_name);
LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}, {:x}", m_type, m_name, ptr);
} }
void CScreenshareSession::stop() { void CScreenshareSession::stop() {
@ -53,9 +52,6 @@ void CScreenshareSession::stop() {
} }
void CScreenshareSession::init() { void CScreenshareSession::init() {
uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get());
LOGM(Log::TRACE, "Created screenshare session for ({}): {}, {:x}", m_type, m_name, ptr);
m_shareStopTimer = makeShared<CEventLoopTimer>( m_shareStopTimer = makeShared<CEventLoopTimer>(
std::chrono::milliseconds(500), std::chrono::milliseconds(500),
[this](SP<CEventLoopTimer> self, void* data) { [this](SP<CEventLoopTimer> self, void* data) {
@ -103,7 +99,7 @@ void CScreenshareSession::calculateConstraints() {
m_name = PMONITOR->m_name; m_name = PMONITOR->m_name;
break; break;
case SHARE_WINDOW: case SHARE_WINDOW:
m_bufferSize = (m_window->m_realSize->value() * PMONITOR->m_scale).round(); m_bufferSize = m_window->m_realSize->value().round();
m_name = m_window->m_title; m_name = m_window->m_title;
break; break;
case SHARE_REGION: case SHARE_REGION:
@ -125,7 +121,7 @@ void CScreenshareSession::screenshareEvents(bool startSharing) {
m_sharing = true; m_sharing = true;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("1,{}", m_type)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("1,{}", m_type)});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("1,{},{}", m_type, m_name)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("1,{},{}", m_type, m_name)});
LOGM(Log::INFO, "Started screenshare session for ({}): {}", m_type, m_name); LOGM(Log::INFO, "New screenshare session for ({}): {}", m_type, m_name);
Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name); Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name);
} else if (!startSharing && m_sharing) { } else if (!startSharing && m_sharing) {

View file

@ -3,7 +3,7 @@
#include "color-management-v1.hpp" #include "color-management-v1.hpp"
#include "../helpers/Monitor.hpp" #include "../helpers/Monitor.hpp"
#include "core/Output.hpp" #include "core/Output.hpp"
#include "../helpers/cm/ColorManagement.hpp" #include "types/ColorManagement.hpp"
#include <cstdint> #include <cstdint>
using namespace NColorManagement; using namespace NColorManagement;
@ -388,6 +388,12 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP<CWpColorMana
RESOURCE->m_settings = m_surface->getPreferredImageDescription(); RESOURCE->m_settings = m_surface->getPreferredImageDescription();
m_currentPreferredId = RESOURCE->m_settings->id(); m_currentPreferredId = RESOURCE->m_settings->id();
if (!PROTO::colorManagement->m_debug && RESOURCE->m_settings->value().icc.fd >= 0) {
LOGM(Log::ERR, "FIXME: parse icc profile");
r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported");
return;
}
RESOURCE->resource()->sendReady(m_currentPreferredId); RESOURCE->resource()->sendReady(m_currentPreferredId);
}); });
@ -423,7 +429,7 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
LOGM(Log::TRACE, "Create image description from icc for id {}", id); LOGM(Log::TRACE, "Create image description from icc for id {}", id);
// FIXME actually check completeness // FIXME actually check completeness
if (m_icc.fd < 0 || !m_icc.length) { if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings"); r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings");
return; return;
} }
@ -437,10 +443,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
return; return;
} }
LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_icc.fd, m_icc.offset, m_icc.length, id); LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_settings.icc.fd, m_settings.icc.offset, m_settings.icc.length, id);
// FIXME actually check support // FIXME actually check support
if (m_icc.fd < 0 || !m_icc.length) { if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported"); RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported");
return; return;
} }
@ -453,9 +459,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
}); });
m_resource->setSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) { m_resource->setSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) {
m_icc.fd = fd; m_settings.icc.fd = fd;
m_icc.offset = offset; m_settings.icc.offset = offset;
m_icc.length = length; m_settings.icc.length = length;
}); });
} }
@ -725,9 +731,8 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP<CW
const auto toProto = [](float value) { return sc<int32_t>(std::round(value * PRIMARIES_SCALE)); }; const auto toProto = [](float value) { return sc<int32_t>(std::round(value * PRIMARIES_SCALE)); };
// FIXME: if (m_settings.icc.fd >= 0)
// if (m_icc.fd >= 0) m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length);
// m_resource->sendIccFile(m_icc.fd, m_icc.length);
// send preferred client paramateres // send preferred client paramateres
m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x), m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x),

View file

@ -7,7 +7,7 @@
#include "../helpers/Monitor.hpp" #include "../helpers/Monitor.hpp"
#include "core/Compositor.hpp" #include "core/Compositor.hpp"
#include "color-management-v1.hpp" #include "color-management-v1.hpp"
#include "../helpers/cm/ColorManagement.hpp" #include "types/ColorManagement.hpp"
class CColorManager; class CColorManager;
class CColorManagementOutput; class CColorManagementOutput;
@ -109,14 +109,6 @@ class CColorManagementIccCreator {
WP<CColorManagementIccCreator> m_self; WP<CColorManagementIccCreator> m_self;
NColorManagement::SImageDescription m_settings; NColorManagement::SImageDescription m_settings;
struct SIccFile {
int fd = -1;
uint32_t length = 0;
uint32_t offset = 0;
bool operator==(const SIccFile& i2) const {
return fd == i2.fd;
}
} m_icc;
private: private:
SP<CWpImageDescriptionCreatorIccV1> m_resource; SP<CWpImageDescriptionCreatorIccV1> m_resource;

View file

@ -56,12 +56,6 @@ CGammaControl::CGammaControl(SP<CZwlrGammaControlV1> resource_, wl_resource* out
LOGM(Log::DEBUG, "setGamma for {}", m_monitor->m_name); LOGM(Log::DEBUG, "setGamma for {}", m_monitor->m_name);
if UNLIKELY (m_monitor->gammaRampsInUse()) {
LOGM(Log::ERR, "Monitor has gamma ramps in use (ICC?)");
m_resource->sendFailed();
return;
}
// TODO: make CFileDescriptor getflags use F_GETFL // TODO: make CFileDescriptor getflags use F_GETFL
int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0); int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0);
if UNLIKELY (fdFlags < 0) { if UNLIKELY (fdFlags < 0) {

View file

@ -13,7 +13,10 @@ CScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_) : m
return; return;
m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); });
m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) {
Screenshare::mgr()->destroyClientSessions(m_savedClient);
PROTO::screencopy->destroyResource(this);
});
m_resource->setCaptureOutput( m_resource->setCaptureOutput(
[this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); }); [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); });
m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w, m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w,
@ -22,6 +25,10 @@ CScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_) : m
m_savedClient = m_resource->client(); m_savedClient = m_resource->client();
} }
CScreencopyClient::~CScreencopyClient() {
Screenshare::mgr()->destroyClientSessions(m_savedClient);
}
void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) { void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) {
const auto PMONITORRES = CWLOutputResource::fromResource(output); const auto PMONITORRES = CWLOutputResource::fromResource(output);
if (!PMONITORRES || !PMONITORRES->m_monitor) { if (!PMONITORRES || !PMONITORRES->m_monitor) {
@ -62,6 +69,11 @@ CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScr
m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); }); m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); });
m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); }); m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); });
m_listeners.stopped = m_session->m_events.stopped.listen([this]() {
if (good())
m_resource->sendFailed();
});
m_frame = m_session->nextFrame(overlayCursor); m_frame = m_session->nextFrame(overlayCursor);
auto formats = m_session->allowedFormats(); auto formats = m_session->allowedFormats();
@ -75,14 +87,7 @@ CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScr
auto bufSize = m_frame->bufferSize(); auto bufSize = m_frame->bufferSize();
const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format); const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format);
const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x);
if (!PSHMINFO) {
LOGM(Log::ERR, "No pixel format for drm format");
m_resource->sendFailed();
return;
}
const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x);
m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride);
if (m_resource->version() >= 3) { if (m_resource->version() >= 3) {
@ -99,12 +104,6 @@ void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* b
return; return;
} }
if UNLIKELY (m_session.expired() || !m_session->monitor()) {
LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this);
m_resource->sendFailed();
return;
}
if UNLIKELY (m_buffer) { if UNLIKELY (m_buffer) {
LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this);
m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used");

View file

@ -19,6 +19,7 @@ namespace Screenshare {
class CScreencopyClient { class CScreencopyClient {
public: public:
CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_); CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_);
~CScreencopyClient();
bool good(); bool good();
@ -51,7 +52,10 @@ class CScreencopyFrame {
Time::steady_tp m_timestamp; Time::steady_tp m_timestamp;
bool m_overlayCursor = true; bool m_overlayCursor = true;
// struct {
CHyprSignalListener stopped;
} m_listeners;
void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage);
friend class CScreencopyProtocol; friend class CScreencopyProtocol;

View file

@ -12,11 +12,11 @@ CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColo
m_opaque = col_.a >= 1.F; m_opaque = col_.a >= 1.F;
m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc<uint8_t*>(&m_color), 4, Vector2D{1, 1}); m_texture = makeShared<CTexture>(DRM_FORMAT_ARGB8888, rc<uint8_t*>(&m_color), 4, Vector2D{1, 1});
m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(client, 1, id)); m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(client, 1, id));
m_success = m_texture->ok(); m_success = m_texture->m_texID;
size = {1, 1}; size = {1, 1};

View file

@ -13,7 +13,10 @@ CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1
if UNLIKELY (!good()) if UNLIKELY (!good())
return; return;
m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) {
Screenshare::mgr()->destroyClientSessions(m_savedClient);
PROTO::toplevelExport->destroyResource(this);
});
m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); });
m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) { m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) {
captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle));
@ -25,6 +28,10 @@ CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1
m_savedClient = m_resource->client(); m_savedClient = m_resource->client();
} }
CToplevelExportClient::~CToplevelExportClient() {
Screenshare::mgr()->destroyClientSessions(m_savedClient);
}
void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) {
auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle);
@ -56,6 +63,11 @@ CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> re
m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); });
m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); }); m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); });
m_listeners.stopped = m_session->m_events.stopped.listen([this]() {
if (good())
m_resource->sendFailed();
});
m_frame = m_session->nextFrame(overlayCursor); m_frame = m_session->nextFrame(overlayCursor);
auto formats = m_session->allowedFormats(); auto formats = m_session->allowedFormats();
@ -88,12 +100,6 @@ void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) {
return; return;
} }
if UNLIKELY (m_session.expired() || !m_session->monitor()) {
LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this);
m_resource->sendFailed();
return;
}
if UNLIKELY (m_buffer) { if UNLIKELY (m_buffer) {
LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this);
m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used");

View file

@ -18,6 +18,7 @@ namespace Screenshare {
class CToplevelExportClient { class CToplevelExportClient {
public: public:
CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_); CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_);
~CToplevelExportClient();
bool good(); bool good();
@ -49,7 +50,10 @@ class CToplevelExportFrame {
CHLBufferReference m_buffer; CHLBufferReference m_buffer;
Time::steady_tp m_timestamp; Time::steady_tp m_timestamp;
// struct {
CHyprSignalListener stopped;
} m_listeners;
void shareFrame(wl_resource* buffer, bool ignoreDamage); void shareFrame(wl_resource* buffer, bool ignoreDamage);
friend class CToplevelExportProtocol; friend class CToplevelExportProtocol;

View file

@ -20,7 +20,7 @@
#include "../../helpers/math/Math.hpp" #include "../../helpers/math/Math.hpp"
#include "../../helpers/time/Time.hpp" #include "../../helpers/time/Time.hpp"
#include "../types/Buffer.hpp" #include "../types/Buffer.hpp"
#include "../../helpers/cm/ColorManagement.hpp" #include "../types/ColorManagement.hpp"
#include "../types/SurfaceRole.hpp" #include "../types/SurfaceRole.hpp"
#include "../types/SurfaceState.hpp" #include "../types/SurfaceState.hpp"

View file

@ -26,7 +26,7 @@ class IHLBuffer : public Aquamarine::IBuffer {
void onBackendRelease(const std::function<void()>& fn); void onBackendRelease(const std::function<void()>& fn);
void addReleasePoint(CDRMSyncPointState& point); void addReleasePoint(CDRMSyncPointState& point);
SP<ITexture> m_texture; SP<CTexture> m_texture;
bool m_opaque = false; bool m_opaque = false;
SP<CWLBufferResource> m_resource; SP<CWLBufferResource> m_resource;
std::vector<UP<CSyncReleaser>> m_syncReleasers; std::vector<UP<CSyncReleaser>> m_syncReleasers;

Some files were not shown because too many files have changed in this diff Show more