Compare commits
64 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b41882c169 | |||
| bcab43b181 | |||
| 08e215c9bf | |||
|
|
4152ac76d0 | ||
|
|
a5858018d8 | ||
|
|
8685fd7b0c | ||
|
|
1fa157cf6d | ||
|
|
e0c5710059 | ||
|
|
42f0a6005b | ||
|
|
ae9ca17b40 | ||
|
|
972f23efe8 | ||
|
|
4c60d9df70 | ||
|
|
b7dfb47566 | ||
|
|
3284dd729b | ||
|
|
803e81ac39 | ||
|
|
34c7cc7d38 | ||
|
|
c47ae950f4 | ||
|
|
3f169ee5de | ||
|
|
10754745a9 | ||
|
|
8271cfc97b | ||
|
|
c11cadd8d6 | ||
|
|
dc4b082ee8 | ||
|
|
edf7098345 | ||
|
|
7299a3b0d5 | ||
|
|
b06a4b5e13 | ||
|
|
3faddf40d0 | ||
|
|
a6e3a2478c | ||
|
|
ff0b706ea3 | ||
|
|
4f44df7b17 | ||
|
|
be03497b82 | ||
|
|
ff20cbf89c | ||
|
|
fe0a202137 | ||
|
|
75a815fbf2 | ||
|
|
3b7401b065 | ||
|
|
d4d17d5d52 | ||
|
|
52ece2b017 | ||
|
|
d98f7ffaf5 | ||
|
|
5f650f8ed9 | ||
|
|
9f98f7440b | ||
|
|
5cb1281035 | ||
|
|
743dffd638 | ||
|
|
5c370c3333 | ||
|
|
cf0d256c13 | ||
|
|
6ebafcf107 | ||
|
|
8ad96a95d6 | ||
|
|
f41e3c2203 | ||
|
|
f0a80ce5e0 | ||
|
|
2928d6af0a | ||
|
|
93aacfc0dc | ||
|
|
19c263e53c | ||
|
|
a032090098 | ||
|
|
0b55c55f4a | ||
|
|
85c2764f5e | ||
|
|
c2bed4103c | ||
|
|
82729db330 | ||
|
|
f12904e641 | ||
|
|
b90c61c04f | ||
|
|
e333a330c0 | ||
|
|
1c64ef06d9 | ||
|
|
6b2c08d3e8 | ||
|
|
db8509dfe2 | ||
|
|
d2b9957fab | ||
|
|
362ea7b0f3 | ||
|
|
f7114016c6 |
176 changed files with 6239 additions and 3162 deletions
|
|
@ -125,6 +125,7 @@ find_package(Threads REQUIRED)
|
|||
|
||||
set(GLES_VERSION "GLES3")
|
||||
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
||||
find_package(glslang CONFIG REQUIRED)
|
||||
|
||||
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
|
||||
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
|
||||
|
|
@ -266,7 +267,8 @@ pkg_check_modules(
|
|||
gbm
|
||||
gio-2.0
|
||||
re2
|
||||
muparser)
|
||||
muparser
|
||||
lcms2)
|
||||
|
||||
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
||||
|
||||
|
|
@ -478,9 +480,9 @@ function(protocolWayland)
|
|||
endfunction()
|
||||
|
||||
if(TARGET OpenGL::GL)
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
else()
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 Threads::Threads)
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -18,6 +18,7 @@ nopch:
|
|||
clear:
|
||||
rm -rf build
|
||||
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
|
||||
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
|
||||
|
||||
all:
|
||||
$(MAKE) clear
|
||||
|
|
|
|||
18
flake.lock
generated
18
flake.lock
generated
|
|
@ -16,11 +16,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1771610171,
|
||||
"narHash": "sha256-+DeInuhbm6a6PpHDNUS7pozDouq2+8xSDefoNaZLW0E=",
|
||||
"lastModified": 1772292445,
|
||||
"narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "7f9eb087703ec4acc6b288d02fa9ea3db803cd3d",
|
||||
"rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -325,11 +325,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1771848320,
|
||||
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
|
||||
"lastModified": 1772198003,
|
||||
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
|
||||
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -348,11 +348,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1771858127,
|
||||
"narHash": "sha256-Gtre9YoYl3n25tJH2AoSdjuwcqij5CPxL3U3xysYD08=",
|
||||
"lastModified": 1772024342,
|
||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "49bbbfc218bf3856dfa631cead3b052d78248b83",
|
||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
206
flake.nix
206
flake.nix
|
|
@ -88,108 +88,122 @@
|
|||
};
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsDebugFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
pkgsDebugCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
in {
|
||||
overlays = import ./nix/overlays.nix {inherit self lib inputs;};
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
|
||||
|
||||
checks = eachSystem (system:
|
||||
(lib.filterAttrs
|
||||
(n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
|
||||
self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = ["subprojects"];
|
||||
always_run = true;
|
||||
checks = eachSystem (
|
||||
system:
|
||||
(lib.filterAttrs (
|
||||
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)
|
||||
) self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = [ "subprojects" ];
|
||||
always_run = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system}));
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system})
|
||||
);
|
||||
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprland;
|
||||
inherit
|
||||
(pkgsFor.${system})
|
||||
# hyprland-packages
|
||||
hyprland
|
||||
hyprland-unwrapped
|
||||
hyprland-with-tests
|
||||
# hyprland-extras
|
||||
xdg-desktop-portal-hyprland
|
||||
;
|
||||
inherit (pkgsDebugFor.${system}) hyprland-debug;
|
||||
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
|
||||
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
|
||||
});
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprland;
|
||||
inherit (pkgsFor.${system})
|
||||
# hyprland-packages
|
||||
hyprland
|
||||
hyprland-unwrapped
|
||||
hyprland-with-tests
|
||||
# hyprland-extras
|
||||
xdg-desktop-portal-hyprland
|
||||
;
|
||||
inherit (pkgsDebugFor.${system}) hyprland-debug;
|
||||
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
|
||||
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
|
||||
});
|
||||
|
||||
devShells = eachSystem (system: {
|
||||
default =
|
||||
pkgsFor.${system}.mkShell.override {
|
||||
inherit (self.packages.${system}.default) stdenv;
|
||||
} {
|
||||
name = "hyprland-shell";
|
||||
hardeningDisable = ["fortify"];
|
||||
inputsFrom = [pkgsFor.${system}.hyprland];
|
||||
packages = [pkgsFor.${system}.clang-tools];
|
||||
inherit (self.checks.${system}.pre-commit-check) shellHook;
|
||||
};
|
||||
});
|
||||
devShells = eachSystem (system: {
|
||||
default =
|
||||
pkgsFor.${system}.mkShell.override
|
||||
{
|
||||
inherit (self.packages.${system}.default) stdenv;
|
||||
}
|
||||
{
|
||||
name = "hyprland-shell";
|
||||
hardeningDisable = [ "fortify" ];
|
||||
inputsFrom = [ pkgsFor.${system}.hyprland ];
|
||||
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;
|
||||
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||
nixosModules.default = import ./nix/module.nix inputs;
|
||||
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||
|
||||
# Hydra build jobs
|
||||
# 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
|
||||
# be added by merging, e.g., self.packages // self.devShells.
|
||||
hydraJobs = self.packages;
|
||||
};
|
||||
# Hydra build jobs
|
||||
# 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
|
||||
# be added by merging, e.g., self.packages // self.devShells.
|
||||
hydraJobs = self.packages;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
|
|||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
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) {
|
||||
// read all data until server closes the connection
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (sizeWritten == 0) {
|
||||
// server closed connection, we're done
|
||||
break;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
|
|||
|
||||
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
|
||||
|
||||
find_package(glaze 7.0.0 QUIET)
|
||||
find_package(glaze 6.0.1 QUIET)
|
||||
if (NOT glaze_FOUND)
|
||||
set(GLAZE_VERSION v7.0.0)
|
||||
set(GLAZE_VERSION v6.0.1)
|
||||
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
|
|
|
|||
|
|
@ -131,9 +131,18 @@ bool CPluginManager::createSafeDirectory(const std::string& path) {
|
|||
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) {
|
||||
const auto HLVER = getHyprlandVersion();
|
||||
|
||||
if (!validArg(url) || !validArg(rev)) {
|
||||
std::println(stderr, "\n{}", failureString("url or rev invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
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"));
|
||||
return false;
|
||||
|
|
@ -198,7 +207,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
|
||||
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")) {
|
||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
|
||||
|
|
@ -503,11 +512,11 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
|
||||
|
||||
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)) {
|
||||
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")) {
|
||||
|
|
@ -648,7 +657,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
const auto HLVER = getHyprlandVersion(false);
|
||||
|
||||
CProgressBar progress;
|
||||
progress.m_iMaxSteps = REPOS.size() * 2 + 2;
|
||||
progress.m_iMaxSteps = (REPOS.size() * 2) + 2;
|
||||
progress.m_iSteps = 0;
|
||||
progress.m_szCurrentMessage = "Updating repositories";
|
||||
progress.print();
|
||||
|
|
@ -669,7 +678,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
|
||||
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")) {
|
||||
std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
|
||||
|
|
@ -679,7 +688,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
if (!repo.rev.empty()) {
|
||||
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) {
|
||||
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class CPluginManager {
|
|||
private:
|
||||
std::string headerError(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);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
#include <src/managers/PointerManager.hpp>
|
||||
#include <src/managers/input/trackpad/TrackpadGestures.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/view/LayerSurface.hpp>
|
||||
#include <src/Compositor.hpp>
|
||||
#include <src/desktop/state/FocusState.hpp>
|
||||
#include <src/layout/LayoutManager.hpp>
|
||||
|
|
@ -270,32 +272,67 @@ static SDispatchResult keybind(std::string in) {
|
|||
return {};
|
||||
}
|
||||
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0;
|
||||
|
||||
//
|
||||
static SDispatchResult addRule(std::string in) {
|
||||
ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
static SDispatchResult addWindowRule(std::string in) {
|
||||
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX)
|
||||
return {.success = false, .error = "re-registering returned a different id?"};
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult checkRule(std::string in) {
|
||||
static SDispatchResult checkWindowRule(std::string in) {
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
|
||||
if (!PLASTWINDOW)
|
||||
return {.success = false, .error = "No window"};
|
||||
|
||||
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
|
||||
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX))
|
||||
return {.success = false, .error = "No rule"};
|
||||
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect")
|
||||
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||
|
||||
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) {
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
|
||||
|
|
@ -325,8 +362,10 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule);
|
||||
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);
|
||||
|
||||
// init mouse
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ namespace Colors {
|
|||
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) \
|
||||
do { \
|
||||
const auto& RESULT = expr; \
|
||||
|
|
|
|||
|
|
@ -118,6 +118,33 @@ static bool test() {
|
|||
Tests::killAllWindows();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,16 +64,16 @@ static void test13349() {
|
|||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,547");
|
||||
EXPECT_CONTAINS(str, "size: 931,511");
|
||||
EXPECT_CONTAINS(str, "at: 497,22");
|
||||
EXPECT_CONTAINS(str, "size: 456,1036");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 967,547");
|
||||
EXPECT_CONTAINS(str, "size: 931,511");
|
||||
EXPECT_CONTAINS(str, "at: 967,22");
|
||||
EXPECT_CONTAINS(str, "size: 456,1036");
|
||||
}
|
||||
|
||||
// clean up
|
||||
|
|
@ -81,6 +81,152 @@ static void test13349() {
|
|||
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() {
|
||||
NLog::log("{}Testing Dwindle layout", Colors::GREEN);
|
||||
|
||||
|
|
@ -91,6 +237,12 @@ static bool test() {
|
|||
NLog::log("{}Testing #13349", Colors::GREEN);
|
||||
test13349();
|
||||
|
||||
NLog::log("{}Testing splits", Colors::GREEN);
|
||||
testSplit();
|
||||
|
||||
NLog::log("{}Testing rotatesplit", Colors::GREEN);
|
||||
testRotatesplit();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
|
|
|
|||
|
|
@ -127,6 +127,34 @@ static bool test() {
|
|||
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);
|
||||
OK(getFromSocket("/keyword group:auto_group false"));
|
||||
|
||||
|
|
@ -173,6 +201,99 @@ static bool test() {
|
|||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
53
hyprtester/src/tests/main/layer.cpp
Normal file
53
hyprtester/src/tests/main/layer.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#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)
|
||||
127
hyprtester/src/tests/main/layout.cpp
Normal file
127
hyprtester/src/tests/main/layout.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#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);
|
||||
|
|
@ -97,6 +97,67 @@ static void focusMasterPrevious() {
|
|||
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() {
|
||||
NLog::log("{}Testing Master layout", Colors::GREEN);
|
||||
|
||||
|
|
@ -108,6 +169,9 @@ static bool test() {
|
|||
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
|
||||
focusMasterPrevious();
|
||||
|
||||
NLog::log("{}Testing fs behavior", Colors::GREEN);
|
||||
testFsBehavior();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
|
|
|||
228
hyprtester/src/tests/main/scroll.cpp
Normal file
228
hyprtester/src/tests/main/scroll.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#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);
|
||||
|
|
@ -93,9 +93,9 @@ static void testSwapWindow() {
|
|||
{
|
||||
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos);
|
||||
|
||||
OK(getFromSocket("/dispatch swapwindow l"));
|
||||
OK(getFromSocket("/dispatch swapwindow r"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||
|
|
@ -566,6 +566,98 @@ static bool testWindowRuleFocusOnActivate() {
|
|||
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() {
|
||||
NLog::log("{}Testing windows", Colors::GREEN);
|
||||
|
||||
|
|
@ -994,7 +1086,7 @@ static bool test() {
|
|||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
|
||||
|
|
@ -1002,12 +1094,12 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
|
||||
|
|
@ -1016,7 +1108,7 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
|
@ -1028,6 +1120,8 @@ static bool test() {
|
|||
testGroupFallbackFocus();
|
||||
testInitialFloatSize();
|
||||
testWindowRuleFocusOnActivate();
|
||||
testPinnedWorkspacesValid();
|
||||
testWindowRuleWorkspaceEmpty();
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
|
|
|||
|
|
@ -255,6 +255,144 @@ static void testMultimonBAF() {
|
|||
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() {
|
||||
NLog::log("{}Testing workspaces", Colors::GREEN);
|
||||
|
||||
|
|
@ -594,13 +732,14 @@ static bool test() {
|
|||
Tests::killAllWindows();
|
||||
|
||||
testMultimonBAF();
|
||||
testMultimonFocus();
|
||||
|
||||
// destroy the headless output
|
||||
OK(getFromSocket("/output remove HEADLESS-3"));
|
||||
|
||||
testSpecialWorkspaceFullscreen();
|
||||
|
||||
testAsymmetricGaps();
|
||||
testDynamicWsEffects();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <cerrno>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
#include "../shared.hpp"
|
||||
#include "../hyprctlCompat.hpp"
|
||||
|
||||
|
|
@ -39,6 +40,38 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
|
|||
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) {
|
||||
errno = 0;
|
||||
int ret = kill(pid, 0);
|
||||
|
|
@ -96,6 +129,38 @@ 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) {
|
||||
CProcess proc("/bin/sh", {"-c", cmd});
|
||||
|
||||
|
|
@ -105,3 +170,14 @@ std::string Tests::execAndGet(const std::string& cmd) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@
|
|||
//NOLINTNEXTLINE
|
||||
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> spawnLayerKitty(const std::string& namespace_ = "", const std::vector<std::string> args = {});
|
||||
bool processAlive(pid_t pid);
|
||||
int windowCount();
|
||||
int countOccurrences(const std::string& in, const std::string& what);
|
||||
bool killAllWindows();
|
||||
void waitUntilWindowsN(int n);
|
||||
int layerCount();
|
||||
bool killAllLayers();
|
||||
std::string execAndGet(const std::string& cmd);
|
||||
bool writeFile(const std::string& name, const std::string& contents);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -179,6 +179,17 @@ 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
|
||||
misc {
|
||||
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
|
||||
|
|
@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager
|
|||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
bind = $mainMod, J, togglesplit, # dwindle
|
||||
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
epoll-shim,
|
||||
git,
|
||||
glaze-hyprland,
|
||||
glslang,
|
||||
gtest,
|
||||
hyprcursor,
|
||||
hyprgraphics,
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
hyprutils,
|
||||
hyprwayland-scanner,
|
||||
hyprwire,
|
||||
lcms2,
|
||||
libGL,
|
||||
libdrm,
|
||||
libexecinfo,
|
||||
|
|
@ -64,8 +66,20 @@
|
|||
inherit (builtins) foldl' readFile;
|
||||
inherit (lib.asserts) assertMsg;
|
||||
inherit (lib.attrsets) mapAttrsToList;
|
||||
inherit (lib.lists) flatten concatLists optional optionals;
|
||||
inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
|
||||
inherit
|
||||
(lib.lists)
|
||||
flatten
|
||||
concatLists
|
||||
optional
|
||||
optionals
|
||||
;
|
||||
inherit
|
||||
(lib.strings)
|
||||
makeBinPath
|
||||
optionalString
|
||||
cmakeBool
|
||||
trim
|
||||
;
|
||||
fs = lib.fileset;
|
||||
|
||||
adapters = flatten [
|
||||
|
|
@ -77,7 +91,8 @@
|
|||
in
|
||||
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
|
||||
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
|
||||
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
|
||||
assert assertMsg (!hidpiXWayland)
|
||||
"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 (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
|
||||
customStdenv.mkDerivation (finalAttrs: {
|
||||
|
|
@ -90,24 +105,29 @@ in
|
|||
fs.intersection
|
||||
# allows non-flake builds to only include files tracked by git
|
||||
(fs.gitTracked ../.)
|
||||
(fs.unions (flatten [
|
||||
../assets/hyprland-portals.conf
|
||||
../assets/install
|
||||
../hyprctl
|
||||
../hyprland.pc.in
|
||||
../hyprpm
|
||||
../LICENSE
|
||||
../protocols
|
||||
../src
|
||||
../start
|
||||
../systemd
|
||||
../VERSION
|
||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||
(optional withTests [../tests ../hyprtester])
|
||||
]));
|
||||
(
|
||||
fs.unions (flatten [
|
||||
../assets/hyprland-portals.conf
|
||||
../assets/install
|
||||
../hyprctl
|
||||
../hyprland.pc.in
|
||||
../hyprpm
|
||||
../LICENSE
|
||||
../protocols
|
||||
../src
|
||||
../start
|
||||
../systemd
|
||||
../VERSION
|
||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||
(optional withTests [
|
||||
../tests
|
||||
../hyprtester
|
||||
])
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
|
|
@ -123,7 +143,10 @@ in
|
|||
GIT_COMMITS = revCount;
|
||||
GIT_COMMIT_DATE = date;
|
||||
GIT_COMMIT_HASH = commit;
|
||||
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
|
||||
GIT_DIRTY =
|
||||
if (commit == "")
|
||||
then "clean"
|
||||
else "dirty";
|
||||
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
||||
};
|
||||
|
||||
|
|
@ -151,6 +174,7 @@ in
|
|||
cairo
|
||||
git
|
||||
glaze-hyprland
|
||||
glslang
|
||||
gtest
|
||||
hyprcursor
|
||||
hyprgraphics
|
||||
|
|
@ -158,6 +182,7 @@ in
|
|||
hyprlang
|
||||
hyprutils
|
||||
hyprwire
|
||||
lcms2
|
||||
libdrm
|
||||
libgbm
|
||||
libGL
|
||||
|
|
@ -218,12 +243,14 @@ in
|
|||
postInstall = ''
|
||||
${optionalString wrapRuntimeDeps ''
|
||||
wrapProgram $out/bin/Hyprland \
|
||||
--suffix PATH : ${makeBinPath [
|
||||
binutils
|
||||
hyprland-guiutils
|
||||
pciutils
|
||||
pkgconf
|
||||
]}
|
||||
--suffix PATH : ${
|
||||
makeBinPath [
|
||||
binutils
|
||||
hyprland-guiutils
|
||||
pciutils
|
||||
pkgconf
|
||||
]
|
||||
}
|
||||
''}
|
||||
|
||||
${optionalString withTests ''
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
writeShellApplication,
|
||||
deadnix,
|
||||
statix,
|
||||
alejandra,
|
||||
nixfmt,
|
||||
llvmPackages_19,
|
||||
fd,
|
||||
}:
|
||||
|
|
@ -11,7 +11,7 @@ writeShellApplication {
|
|||
runtimeInputs = [
|
||||
deadnix
|
||||
statix
|
||||
alejandra
|
||||
nixfmt
|
||||
llvmPackages_19.clang-tools
|
||||
fd
|
||||
];
|
||||
|
|
@ -24,14 +24,14 @@ writeShellApplication {
|
|||
nix_format() {
|
||||
if [ "$*" = 0 ]; then
|
||||
fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
elif [ -d "$1" ]; then
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
else
|
||||
statix fix -- "$1"
|
||||
deadnix -e "$1"
|
||||
alejandra "$1"
|
||||
nixfmt "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
self: {
|
||||
config,
|
||||
self:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
|
||||
package = self.packages.${system}.default;
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = {
|
||||
wayland.windowManager.hyprland.package = lib.mkDefault package;
|
||||
};
|
||||
|
|
|
|||
115
nix/lib.nix
115
nix/lib.nix
|
|
@ -1,4 +1,5 @@
|
|||
lib: let
|
||||
lib:
|
||||
let
|
||||
inherit (lib)
|
||||
attrNames
|
||||
filterAttrs
|
||||
|
|
@ -17,7 +18,7 @@ lib: let
|
|||
|
||||
This function takes a nested attribute set and converts it into Hyprland-compatible
|
||||
configuration syntax, supporting top, bottom, and regular command sections.
|
||||
|
||||
|
||||
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
|
||||
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
|
||||
|
||||
|
|
@ -81,44 +82,51 @@ lib: let
|
|||
|
||||
:::
|
||||
*/
|
||||
toHyprlang = {
|
||||
topCommandsPrefixes ? ["$" "bezier"],
|
||||
bottomCommandsPrefixes ? [],
|
||||
}: attrs: let
|
||||
toHyprlang' = 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
|
||||
};
|
||||
toHyprlang =
|
||||
{
|
||||
topCommandsPrefixes ? [
|
||||
"$"
|
||||
"bezier"
|
||||
],
|
||||
bottomCommandsPrefixes ? [ ],
|
||||
}:
|
||||
attrs:
|
||||
let
|
||||
toHyprlang' =
|
||||
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"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
|
||||
# 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;
|
||||
# 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;
|
||||
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
toHyprlang' attrs;
|
||||
|
||||
/**
|
||||
|
|
@ -131,7 +139,7 @@ lib: let
|
|||
Configuration:
|
||||
|
||||
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
|
||||
|
||||
|
||||
# Inputs
|
||||
|
||||
Structured function argument:
|
||||
|
|
@ -139,7 +147,7 @@ lib: let
|
|||
: pred (required)
|
||||
: A function that determines how parent and child keys should be combined into a single key.
|
||||
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
|
||||
|
||||
|
||||
Value:
|
||||
|
||||
: The nested attribute set to be flattened.
|
||||
|
|
@ -174,26 +182,21 @@ lib: let
|
|||
```
|
||||
|
||||
:::
|
||||
|
||||
*/
|
||||
flattenAttrs = pred: attrs: let
|
||||
flattenAttrs' = prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key: let
|
||||
value = attrs.${key};
|
||||
newKey =
|
||||
if prefix == ""
|
||||
then key
|
||||
else pred prefix key;
|
||||
in
|
||||
acc
|
||||
// (
|
||||
if builtins.isAttrs value
|
||||
then flattenAttrs' newKey value
|
||||
else {"${newKey}" = value;}
|
||||
)
|
||||
) {} (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs =
|
||||
pred: attrs:
|
||||
let
|
||||
flattenAttrs' =
|
||||
prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key:
|
||||
let
|
||||
value = attrs.${key};
|
||||
newKey = if prefix == "" then key else pred prefix key;
|
||||
in
|
||||
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
|
||||
) { } (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs' "" attrs;
|
||||
in
|
||||
{
|
||||
|
|
|
|||
111
nix/module.nix
111
nix/module.nix
|
|
@ -1,18 +1,21 @@
|
|||
inputs: {
|
||||
inputs:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
selflib = import ./lib.nix lib;
|
||||
cfg = config.programs.hyprland;
|
||||
in {
|
||||
in
|
||||
{
|
||||
options = {
|
||||
programs.hyprland = {
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of Hyprland plugins to use. Can either be packages or
|
||||
absolute plugin paths.
|
||||
|
|
@ -20,23 +23,25 @@ in {
|
|||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = with lib.types; let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
type =
|
||||
with lib.types;
|
||||
let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
valueType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Hyprland configuration written in Nix. Entries with the same key
|
||||
should be written as lists. Variables' and colors' names should be
|
||||
|
|
@ -92,8 +97,15 @@ in {
|
|||
|
||||
topPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = ["$" "bezier"];
|
||||
example = ["$" "bezier" "source"];
|
||||
default = [
|
||||
"$"
|
||||
"bezier"
|
||||
];
|
||||
example = [
|
||||
"$"
|
||||
"bezier"
|
||||
"source"
|
||||
];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the top of the config.
|
||||
'';
|
||||
|
|
@ -101,8 +113,8 @@ in {
|
|||
|
||||
bottomPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [];
|
||||
example = ["source"];
|
||||
default = [ ];
|
||||
example = [ "source" ];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the bottom of the config.
|
||||
'';
|
||||
|
|
@ -117,35 +129,36 @@ in {
|
|||
};
|
||||
}
|
||||
(lib.mkIf cfg.enable {
|
||||
environment.etc."xdg/hypr/hyprland.conf" = let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
|
||||
environment.etc."xdg/hypr/hyprland.conf" =
|
||||
let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ];
|
||||
|
||||
pluginsToHyprlang = plugins:
|
||||
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 {
|
||||
pluginsToHyprlang =
|
||||
_plugins:
|
||||
selflib.toHyprlang
|
||||
{
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
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;
|
||||
};
|
||||
})
|
||||
|
|
|
|||
115
nix/overlays.nix
115
nix/overlays.nix
|
|
@ -2,20 +2,27 @@
|
|||
self,
|
||||
lib,
|
||||
inputs,
|
||||
}: let
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
}:
|
||||
let
|
||||
mkDate =
|
||||
longDate:
|
||||
(lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
|
||||
in {
|
||||
in
|
||||
{
|
||||
# Contains what a user is most likely to care about:
|
||||
# Hyprland itself, XDPH and the Share Picker.
|
||||
default = lib.composeManyExtensions (with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
]);
|
||||
default = lib.composeManyExtensions (
|
||||
with self.overlays;
|
||||
[
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
]
|
||||
);
|
||||
|
||||
# Packages for variations of Hyprland, dependencies included.
|
||||
hyprland-packages = lib.composeManyExtensions [
|
||||
|
|
@ -33,49 +40,45 @@ in {
|
|||
self.overlays.glaze
|
||||
|
||||
# Hyprland packages themselves
|
||||
(final: _prev: let
|
||||
date = mkDate (self.lastModifiedDate or "19700101");
|
||||
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
|
||||
in {
|
||||
hyprland = final.callPackage ./default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
commit = self.rev or "";
|
||||
revCount = self.sourceInfo.revCount or "";
|
||||
inherit date version;
|
||||
};
|
||||
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
|
||||
(
|
||||
final: _prev:
|
||||
let
|
||||
date = mkDate (self.lastModifiedDate or "19700101");
|
||||
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
|
||||
in
|
||||
{
|
||||
hyprland = final.callPackage ./default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
commit = self.rev or "";
|
||||
revCount = self.sourceInfo.revCount or "";
|
||||
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.
|
||||
Hyprtester is always built now.
|
||||
''
|
||||
final.hyprland;
|
||||
'' final.hyprland;
|
||||
|
||||
# deprecated packages
|
||||
hyprland-legacy-renderer =
|
||||
builtins.trace ''
|
||||
# deprecated packages
|
||||
hyprland-legacy-renderer = builtins.trace ''
|
||||
hyprland-legacy-renderer was removed. Please use the hyprland package.
|
||||
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.
|
||||
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.
|
||||
For more information, refer to https://wiki.hypr.land/Configuring/XWayland.
|
||||
''
|
||||
final.hyprland;
|
||||
})
|
||||
'' final.hyprland;
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
# Debug
|
||||
|
|
@ -83,10 +86,10 @@ in {
|
|||
# Dependencies
|
||||
self.overlays.hyprland-packages
|
||||
|
||||
(final: prev: {
|
||||
aquamarine = prev.aquamarine.override {debug = true;};
|
||||
hyprutils = prev.hyprutils.override {debug = true;};
|
||||
hyprland-debug = prev.hyprland.override {debug = true;};
|
||||
(_final: prev: {
|
||||
aquamarine = prev.aquamarine.override { debug = true; };
|
||||
hyprutils = prev.hyprutils.override { debug = true; };
|
||||
hyprland-debug = prev.hyprland.override { debug = true; };
|
||||
})
|
||||
];
|
||||
|
||||
|
|
@ -100,21 +103,23 @@ in {
|
|||
# this version is the one used in the git submodule, and allows us to
|
||||
# fetch the source without '?submodules=1'
|
||||
udis86 = final: prev: {
|
||||
udis86-hyprland = prev.udis86.overrideAttrs (_self: _super: {
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "canihavesomecoffee";
|
||||
repo = "udis86";
|
||||
rev = "5336633af70f3917760a6d441ff02d93477b0c86";
|
||||
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
|
||||
};
|
||||
udis86-hyprland = prev.udis86.overrideAttrs (
|
||||
_self: _super: {
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "canihavesomecoffee";
|
||||
repo = "udis86";
|
||||
rev = "5336633af70f3917760a6d441ff02d93477b0c86";
|
||||
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
|
||||
};
|
||||
|
||||
patches = [];
|
||||
});
|
||||
patches = [ ];
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
# 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
|
||||
glaze = final: prev: {
|
||||
glaze = _final: prev: {
|
||||
glaze-hyprland = prev.glaze.override {
|
||||
enableSSL = false;
|
||||
enableInterop = false;
|
||||
|
|
|
|||
|
|
@ -1,71 +1,75 @@
|
|||
inputs: pkgs: let
|
||||
inputs: pkgs:
|
||||
let
|
||||
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
|
||||
hyprland = flake.hyprland-with-tests;
|
||||
in {
|
||||
in
|
||||
{
|
||||
tests = pkgs.testers.runNixOSTest {
|
||||
name = "hyprland-tests";
|
||||
|
||||
nodes.machine = {pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xeyes
|
||||
];
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xeyes
|
||||
];
|
||||
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"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;
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||
};
|
||||
|
||||
# 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 = ''
|
||||
# Wait for tty to be up
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/rend
|
|||
for filename in `ls ${SHADERS_SRC}`; do
|
||||
echo "-- ${filename}"
|
||||
|
||||
{ echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
{ echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
|
||||
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
|
||||
echo "}," >> ./src/render/shaders/Shaders.hpp
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include "protocols/core/Compositor.hpp"
|
||||
#include "protocols/core/Subcompositor.hpp"
|
||||
#include "desktop/view/LayerSurface.hpp"
|
||||
#include "layout/space/Space.hpp"
|
||||
#include "render/Renderer.hpp"
|
||||
#include "xwayland/XWayland.hpp"
|
||||
#include "helpers/ByteOperations.hpp"
|
||||
|
|
@ -1404,6 +1405,35 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
|
|||
PHLWINDOW leaderWindow = nullptr;
|
||||
|
||||
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) {
|
||||
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
|
||||
continue;
|
||||
|
|
@ -1426,24 +1456,20 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
|
|||
|
||||
switch (dir) {
|
||||
case Math::DIRECTION_LEFT:
|
||||
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
|
||||
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.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));
|
||||
}
|
||||
break;
|
||||
case Math::DIRECTION_RIGHT:
|
||||
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
|
||||
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))
|
||||
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
|
||||
}
|
||||
break;
|
||||
case Math::DIRECTION_UP:
|
||||
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
|
||||
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.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));
|
||||
}
|
||||
break;
|
||||
case Math::DIRECTION_DOWN:
|
||||
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
|
||||
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))
|
||||
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
|
@ -1799,6 +1825,9 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
|
|||
g_layoutManager->recalculateMonitor(pMonitorA);
|
||||
g_layoutManager->recalculateMonitor(pMonitorB);
|
||||
|
||||
g_pHyprRenderer->damageMonitor(pMonitorB);
|
||||
g_pHyprRenderer->damageMonitor(pMonitorA);
|
||||
|
||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
|
||||
PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
|
||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
|
||||
|
|
@ -1953,6 +1982,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
|
||||
// move the workspace
|
||||
pWorkspace->m_monitor = pMonitor;
|
||||
pWorkspace->m_space->recheckWorkArea();
|
||||
pWorkspace->m_events.monitorChanged.emit();
|
||||
|
||||
for (auto const& w : m_windows) {
|
||||
|
|
@ -2008,6 +2038,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
pWorkspace->m_events.activeChanged.emit();
|
||||
|
||||
g_layoutManager->recalculateMonitor(pMonitor);
|
||||
g_pHyprRenderer->damageMonitor(pMonitor);
|
||||
|
||||
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
pWorkspace->m_visible = true;
|
||||
|
|
@ -2747,6 +2778,7 @@ void CCompositor::arrangeMonitors() {
|
|||
}
|
||||
|
||||
PROTO::xdgOutput->updateAllOutputs();
|
||||
Event::bus()->m_events.monitor.layoutChanged.emit();
|
||||
|
||||
#ifndef NO_XWAYLAND
|
||||
const auto box = g_pCompositor->calculateX11WorkArea();
|
||||
|
|
@ -3042,6 +3074,27 @@ 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() {
|
||||
if (!m_aqBackend->hasSession())
|
||||
return std::nullopt;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "managers/KeybindManager.hpp"
|
||||
#include "managers/SessionLockManager.hpp"
|
||||
#include "desktop/view/Window.hpp"
|
||||
#include "protocols/types/ColorManagement.hpp"
|
||||
#include "helpers/cm/ColorManagement.hpp"
|
||||
|
||||
#include <aquamarine/backend/Backend.hpp>
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
@ -161,6 +161,7 @@ class CCompositor {
|
|||
void updateSuspendedStates();
|
||||
void onNewMonitor(SP<Aquamarine::IOutput> output);
|
||||
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
||||
void ensureWorkspacesOnAssignedMonitors();
|
||||
std::optional<unsigned int> getVTNr();
|
||||
bool isVRRActiveOnAnyMonitor() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -1579,6 +1579,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_BOOL,
|
||||
.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:
|
||||
|
|
@ -2093,6 +2105,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_CHOICE,
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -655,6 +655,8 @@ CConfigManager::CConfigManager() {
|
|||
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: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:workspace_wraparound", Hyprlang::INT{0});
|
||||
|
|
@ -797,6 +799,8 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("render:non_shader_cm", Hyprlang::INT{3});
|
||||
registerConfigVar("render:cm_sdr_eotf", {"default"});
|
||||
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_donation_nag", Hyprlang::INT{0});
|
||||
|
|
@ -871,6 +875,7 @@ CConfigManager::CConfigManager() {
|
|||
m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0});
|
||||
m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1});
|
||||
m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1});
|
||||
m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""});
|
||||
|
||||
// windowrule v3
|
||||
m_config->addSpecialCategory("windowrule", {.key = "name"});
|
||||
|
|
@ -1257,6 +1262,10 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
|
|||
if (VAL && VAL->m_bSetByUser)
|
||||
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();
|
||||
|
||||
std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });
|
||||
|
|
@ -2275,6 +2284,15 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) {
|
|||
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() {
|
||||
m_rule.disabled = true;
|
||||
}
|
||||
|
|
@ -2369,6 +2387,9 @@ std::optional<std::string> CConfigManager::handleMonitor(const std::string& comm
|
|||
} else if (ARGS[argno] == "vrr") {
|
||||
parser.parseVRR(std::string(ARGS[argno + 1]));
|
||||
argno++;
|
||||
} else if (ARGS[argno] == "icc") {
|
||||
parser.parseICC(std::string(ARGS[argno + 1]));
|
||||
argno++;
|
||||
} else if (ARGS[argno] == "workspace") {
|
||||
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1]));
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ class CMonitorRuleParser {
|
|||
bool parseSDRBrightness(const std::string& value);
|
||||
bool parseSDRSaturation(const std::string& value);
|
||||
bool parseVRR(const std::string& value);
|
||||
bool parseICC(const std::string& value);
|
||||
|
||||
void setDisabled();
|
||||
void setMirror(const std::string& value);
|
||||
|
|
|
|||
|
|
@ -2049,7 +2049,12 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques
|
|||
}
|
||||
|
||||
static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {
|
||||
if (g_pHyprOpenGL->initShaders())
|
||||
CVarList vars(request, 0, ' ');
|
||||
|
||||
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";
|
||||
else
|
||||
return format == FORMAT_JSON ? "{\"ok\": false}" : "error";
|
||||
|
|
@ -2076,8 +2081,8 @@ CHyprCtl::CHyprCtl() {
|
|||
registerCommand(SHyprCtlCommand{"locked", true, getIsLocked});
|
||||
registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions});
|
||||
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{"reload", false, reloadRequest});
|
||||
registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin});
|
||||
|
|
@ -2197,6 +2202,15 @@ std::string CHyprCtl::getReply(std::string request) {
|
|||
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) {
|
||||
g_pHyprRenderer->damageMonitor(m);
|
||||
}
|
||||
|
|
@ -2210,16 +2224,38 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) {
|
|||
}
|
||||
|
||||
static bool successWrite(int fd, const std::string& data, bool needLog = true) {
|
||||
if (write(fd, data.c_str(), data.length()) > 0)
|
||||
return true;
|
||||
size_t totalWritten = 0;
|
||||
size_t remaining = data.length();
|
||||
size_t waitsDone = 0;
|
||||
constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms
|
||||
|
||||
if (errno == EAGAIN)
|
||||
return true;
|
||||
while (totalWritten < data.length()) {
|
||||
ssize_t written = write(fd, data.c_str() + totalWritten, remaining);
|
||||
|
||||
if (needLog)
|
||||
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
|
||||
if (waitsDone > MAX_WAITS) {
|
||||
Log::logger->log(Log::ERR, "Couldn't write to socket. Buffer was full and the client couldn't read in time.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (written < 0) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include "../desktop/state/FocusState.hpp"
|
||||
|
||||
CHyprDebugOverlay::CHyprDebugOverlay() {
|
||||
m_texture = makeShared<CTexture>();
|
||||
m_texture = g_pHyprRenderer->createTexture();
|
||||
}
|
||||
|
||||
void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {
|
||||
|
|
@ -259,15 +259,7 @@ void CHyprDebugOverlay::draw() {
|
|||
cairo_surface_flush(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, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
||||
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);
|
||||
|
||||
CTexPassElement::SRenderData data;
|
||||
data.tex = m_texture;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class CHyprDebugOverlay {
|
|||
cairo_surface_t* m_cairoSurface = nullptr;
|
||||
cairo_t* m_cairo = nullptr;
|
||||
|
||||
SP<CTexture> m_texture;
|
||||
SP<ITexture> m_texture;
|
||||
|
||||
friend class CHyprMonitorDebugOverlay;
|
||||
friend class CHyprRenderer;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ CHyprNotificationOverlay::CHyprNotificationOverlay() {
|
|||
|
||||
g_pHyprRenderer->damageBox(m_lastDamage);
|
||||
});
|
||||
|
||||
m_texture = makeShared<CTexture>();
|
||||
}
|
||||
|
||||
CHyprNotificationOverlay::~CHyprNotificationOverlay() {
|
||||
|
|
@ -232,16 +230,7 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {
|
|||
|
||||
m_lastDamage = damage;
|
||||
|
||||
// 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);
|
||||
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);
|
||||
|
||||
CTexPassElement::SRenderData data;
|
||||
data.tex = m_texture;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ enum eIconBackend : uint8_t {
|
|||
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>{"", "", "", "", "", "", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
|
||||
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0},
|
||||
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 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{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0},
|
||||
|
|
@ -57,7 +57,7 @@ class CHyprNotificationOverlay {
|
|||
PHLMONITORREF m_lastMonitor;
|
||||
Vector2D m_lastSize = Vector2D(-1, -1);
|
||||
|
||||
SP<CTexture> m_texture;
|
||||
SP<ITexture> m_texture;
|
||||
};
|
||||
|
||||
inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "../../view/LayerSurface.hpp"
|
||||
#include "../../types/OverridableVar.hpp"
|
||||
#include "../../../helpers/MiscFunctions.hpp"
|
||||
#include "../../../event/EventBus.hpp"
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::Rule;
|
||||
|
|
@ -32,11 +33,38 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> prop
|
|||
UNSET(aboveLock)
|
||||
UNSET(ignoreAlpha)
|
||||
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) {
|
||||
for (const auto& [key, effect] : rule->effects()) {
|
||||
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: {
|
||||
Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
|
||||
break;
|
||||
|
|
@ -125,4 +153,7 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRulePropert
|
|||
|
||||
applyDynamicRule(wr);
|
||||
}
|
||||
|
||||
// for plugins
|
||||
Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "LayerRuleEffectContainer.hpp"
|
||||
#include "../../DesktopTypes.hpp"
|
||||
#include "../Rule.hpp"
|
||||
#include "../../types/OverridableVar.hpp"
|
||||
|
|
@ -21,6 +22,17 @@ namespace Desktop::Rule {
|
|||
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
|
||||
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 DEFINE_PROP(type, name, def) \
|
||||
private: \
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
|
|||
return false;
|
||||
break;
|
||||
case RULE_PROP_CONTENT:
|
||||
if (!engine->match(w->getContentType()))
|
||||
if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType())))
|
||||
return false;
|
||||
break;
|
||||
case RULE_PROP_XDG_TAG:
|
||||
|
|
|
|||
|
|
@ -630,6 +630,8 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProper
|
|||
needsRelayout = needsRelayout || RES.needsRelayout;
|
||||
}
|
||||
|
||||
m_window->updateWindowData();
|
||||
m_window->updateWindowDecos();
|
||||
m_window->updateDecorationValues();
|
||||
|
||||
if (needsRelayout)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ void CGroup::add(PHLWINDOW w) {
|
|||
m_target->recalc();
|
||||
}
|
||||
|
||||
void CGroup::remove(PHLWINDOW w) {
|
||||
void CGroup::remove(PHLWINDOW w, Math::eDirection dir) {
|
||||
std::optional<size_t> idx;
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
if (m_windows.at(i) == w) {
|
||||
|
|
@ -156,8 +156,20 @@ void CGroup::remove(PHLWINDOW w) {
|
|||
updateWindowVisibility();
|
||||
|
||||
// do this here: otherwise the new current is hidden and workspace rules get wrong data
|
||||
if (!REMOVING_GROUP)
|
||||
w->m_target->assignToSpace(m_target->space());
|
||||
if (!REMOVING_GROUP) {
|
||||
std::optional<Vector2D> focalPoint;
|
||||
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) {
|
||||
|
|
@ -317,6 +329,7 @@ void CGroup::swapWithNext() {
|
|||
|
||||
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);
|
||||
m_current = idx;
|
||||
|
||||
updateWindowVisibility();
|
||||
|
||||
|
|
@ -329,6 +342,7 @@ void CGroup::swapWithLast() {
|
|||
|
||||
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);
|
||||
m_current = idx;
|
||||
|
||||
updateWindowVisibility();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../DesktopTypes.hpp"
|
||||
#include "../../helpers/math/Direction.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ namespace Desktop::View {
|
|||
bool has(PHLWINDOW w) const;
|
||||
|
||||
void add(PHLWINDOW w);
|
||||
void remove(PHLWINDOW w);
|
||||
void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT);
|
||||
void moveCurrent(bool next);
|
||||
void setCurrent(size_t idx);
|
||||
void setCurrent(PHLWINDOW w);
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
|
|||
|
||||
// fucker fucking fuck
|
||||
const auto WORKAREA = m_workspace->m_space->workArea();
|
||||
const auto& RESERVED = PMONITOR->m_reservedArea;
|
||||
const auto& RESERVED = CReservedArea(PMONITOR->logicalBox(), WORKAREA);
|
||||
|
||||
if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) {
|
||||
POS.x -= RESERVED.left();
|
||||
|
|
@ -510,12 +510,6 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
|
|||
|
||||
setAnimationsToMove();
|
||||
|
||||
OLDWORKSPACE->updateWindows();
|
||||
OLDWORKSPACE->updateWindowData();
|
||||
|
||||
pWorkspace->updateWindows();
|
||||
pWorkspace->updateWindowData();
|
||||
|
||||
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
|
||||
|
||||
if (valid(pWorkspace)) {
|
||||
|
|
@ -807,9 +801,13 @@ void CWindow::updateWindowData() {
|
|||
}
|
||||
|
||||
void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) {
|
||||
m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE);
|
||||
if (workspaceRule.noBorder.value_or(false))
|
||||
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->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->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE);
|
||||
}
|
||||
|
|
@ -1873,11 +1871,12 @@ void CWindow::mapWindow() {
|
|||
if (WORKSPACEARGS.contains("silent"))
|
||||
workspaceSilent = true;
|
||||
|
||||
if (WORKSPACEARGS.contains("empty") && PWORKSPACE->getWindows() <= 1) {
|
||||
auto joined = WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0);
|
||||
if (joined.starts_with("empty") && PWORKSPACE->getWindows() == 0) {
|
||||
requestedWorkspaceID = PWORKSPACE->m_id;
|
||||
requestedWorkspaceName = PWORKSPACE->m_name;
|
||||
} else {
|
||||
auto result = getWorkspaceIDNameFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0));
|
||||
auto result = getWorkspaceIDNameFromString(joined);
|
||||
requestedWorkspaceID = result.id;
|
||||
requestedWorkspaceName = result.name;
|
||||
}
|
||||
|
|
@ -1949,11 +1948,13 @@ void CWindow::mapWindow() {
|
|||
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());
|
||||
|
||||
if (*PAUTOGROUP // auto_group enabled
|
||||
&& Desktop::focusState()->window() // focused window exists
|
||||
&& 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
|
||||
&& !isModal() && !(parent() && m_isFloating) && !isX11OverrideRedirect() // not a modal, floating child or X11 OR
|
||||
if (*PAUTOGROUP // auto_group enabled
|
||||
&& Desktop::focusState()->window() // focused window exists
|
||||
&& 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
|
||||
&& !g_pXWaylandManager->shouldBeFloated(m_self.lock()) && !isX11OverrideRedirect() // not a window that should float or X11
|
||||
&& !(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
|
||||
Desktop::focusState()->window()->m_group->add(m_self.lock());
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ namespace Event {
|
|||
Event<PHLWINDOW> openEarly;
|
||||
Event<PHLWINDOW> destroy;
|
||||
Event<PHLWINDOW> close;
|
||||
Event<PHLWINDOW> kill;
|
||||
Event<PHLWINDOW, Desktop::eFocusReason> active;
|
||||
Event<PHLWINDOW> urgent;
|
||||
Event<PHLWINDOW> title;
|
||||
|
|
@ -54,6 +55,7 @@ namespace Event {
|
|||
struct {
|
||||
Event<PHLLS> opened;
|
||||
Event<PHLLS> closed;
|
||||
Event<PHLLS> updateRules;
|
||||
} layer;
|
||||
|
||||
struct {
|
||||
|
|
@ -139,4 +141,4 @@ namespace Event {
|
|||
};
|
||||
|
||||
UP<CEventBus>& bus();
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -297,31 +297,23 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {
|
|||
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) {
|
||||
auto n = drmGetFormatName(drm);
|
||||
auto n = drmGetFormatName(drm);
|
||||
|
||||
if (!n)
|
||||
return "unknown";
|
||||
|
||||
std::string name = n;
|
||||
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string NFormatUtils::drmModifierName(uint64_t mod) {
|
||||
auto n = drmGetFormatModifierName(mod);
|
||||
auto n = drmGetFormatModifierName(mod);
|
||||
|
||||
if (!n)
|
||||
return "unknown";
|
||||
|
||||
std::string name = n;
|
||||
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
|
||||
return name;
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ namespace NFormatUtils {
|
|||
bool isFormatOpaque(DRMFormat drm);
|
||||
int pixelsPerBlock(const SPixelFormat* const fmt);
|
||||
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 drmModifierName(uint64_t mod);
|
||||
DRMFormat alphaFormat(DRMFormat prevFormat);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#include "../hyprerror/HyprError.hpp"
|
||||
#include "../layout/LayoutManager.hpp"
|
||||
#include "../i18n/Engine.hpp"
|
||||
#include "../protocols/types/ColorManagement.hpp"
|
||||
#include "../helpers/cm/ColorManagement.hpp"
|
||||
#include "sync/SyncTimeline.hpp"
|
||||
#include "time/Time.hpp"
|
||||
#include "../desktop/view/LayerSurface.hpp"
|
||||
|
|
@ -82,7 +82,10 @@ void CMonitor::onConnect(bool noRule) {
|
|||
m_zoomAnimProgress->setValueAndWarp(0.F);
|
||||
m_zoomAnimFrameCounter = 0;
|
||||
|
||||
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
|
||||
g_pEventLoopManager->doLater([] {
|
||||
g_pConfigManager->ensurePersistentWorkspacesPresent();
|
||||
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
|
||||
});
|
||||
|
||||
m_listeners.frame = m_output->events.frame.listen([this] {
|
||||
if (m_frameScheduler)
|
||||
|
|
@ -290,10 +293,16 @@ void CMonitor::onConnect(bool noRule) {
|
|||
if (!valid(ws))
|
||||
continue;
|
||||
|
||||
if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
|
||||
const auto CURRENTMON = ws->m_monitor.lock();
|
||||
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_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
ws->m_lastMonitor = "";
|
||||
if (RETURNING)
|
||||
ws->m_lastMonitor = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,19 +438,24 @@ void CMonitor::onDisconnect(bool destroy) {
|
|||
m_enabled = 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) {
|
||||
// snap cursor
|
||||
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) {
|
||||
w->m_lastMonitor = m_name;
|
||||
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
|
||||
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
}
|
||||
|
|
@ -919,29 +933,46 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|||
m_supportsWideColor = RULE->supportsHDR;
|
||||
m_supportsHDR = RULE->supportsHDR;
|
||||
|
||||
m_cmType = RULE->cmType;
|
||||
switch (m_cmType) {
|
||||
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_HDR:
|
||||
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
|
||||
default: break;
|
||||
if (RULE->iccFile.empty()) {
|
||||
// only apply explicit cm settings if we have no icc file
|
||||
|
||||
m_cmType = RULE->cmType;
|
||||
switch (m_cmType) {
|
||||
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; 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;
|
||||
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
|
||||
// invalid scale, will produce fractional pixels.
|
||||
|
|
@ -1023,6 +1054,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|||
|
||||
m_damage.setSize(m_transformedSize);
|
||||
|
||||
updateVCGTRamps();
|
||||
|
||||
// Set scale for all surfaces on this monitor, needed for some clients
|
||||
// but not on unsafe state to avoid crashes
|
||||
if (!g_pCompositor->m_unsafeState) {
|
||||
|
|
@ -1329,7 +1362,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
|
|||
// move pinned windows
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (w->m_workspace == POLDWORKSPACE && w->m_pinned)
|
||||
w->moveToWorkspace(pWorkspace);
|
||||
w->layoutTarget()->assignToSpace(pWorkspace->m_space);
|
||||
}
|
||||
|
||||
if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&
|
||||
|
|
@ -1448,6 +1481,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|||
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
|
||||
PMWSOWNER->m_activeSpecialWorkspace.reset();
|
||||
g_layoutManager->recalculateMonitor(PMWSOWNER);
|
||||
g_pHyprRenderer->damageMonitor(PMWSOWNER);
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
|
||||
|
||||
|
|
@ -1545,10 +1579,20 @@ Vector2D CMonitor::middle() {
|
|||
return m_position + m_size / 2.f;
|
||||
}
|
||||
|
||||
const Mat3x3& CMonitor::getTransformMatrix() {
|
||||
return m_projMatrix;
|
||||
}
|
||||
|
||||
const Mat3x3& CMonitor::getScaleMatrix() {
|
||||
return m_projOutputMatrix;
|
||||
}
|
||||
|
||||
void CMonitor::updateMatrix() {
|
||||
m_projMatrix = Mat3x3::identity();
|
||||
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_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
}
|
||||
|
||||
WORKSPACEID CMonitor::activeWorkspaceID() {
|
||||
|
|
@ -1835,7 +1879,7 @@ uint16_t CMonitor::isDSBlocked(bool full) {
|
|||
|
||||
// we can't scanout shm buffers.
|
||||
const auto params = PSURFACE->m_current.buffer->dmabuf();
|
||||
if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) {
|
||||
if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) {
|
||||
reasons |= DS_BLOCK_DMA;
|
||||
if (!full)
|
||||
return reasons;
|
||||
|
|
@ -2172,8 +2216,8 @@ bool CMonitor::canNoShaderCM() {
|
|||
|
||||
const auto SRC_DESC_VALUE = SRC_DESC.value()->value();
|
||||
|
||||
if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0)
|
||||
return false; // no ICC support
|
||||
if (m_imageDescription->value().icc.present)
|
||||
return false;
|
||||
|
||||
const auto sdrEOTF = NTransferFunction::fromConfig();
|
||||
// only primaries differ
|
||||
|
|
@ -2192,6 +2236,71 @@ bool CMonitor::doesNoShaderCM() {
|
|||
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) {
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#include "math/Math.hpp"
|
||||
#include "../desktop/reserved/ReservedArea.hpp"
|
||||
#include <optional>
|
||||
#include "../protocols/types/ColorManagement.hpp"
|
||||
#include "cm/ColorManagement.hpp"
|
||||
#include "signal/Signal.hpp"
|
||||
#include "DamageRing.hpp"
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
@ -56,6 +56,7 @@ struct SMonitorRule {
|
|||
float sdrSaturation = 1.0f; // SDR -> HDR
|
||||
float sdrBrightness = 1.0f; // SDR -> HDR
|
||||
Desktop::CReservedArea reservedArea;
|
||||
std::string iccFile;
|
||||
|
||||
int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
|
|
@ -126,7 +127,7 @@ class CMonitor {
|
|||
bool m_scheduledRecalc = false;
|
||||
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
float m_xwaylandScale = 1.f;
|
||||
Mat3x3 m_projMatrix;
|
||||
|
||||
std::optional<Vector2D> m_forceSize;
|
||||
SP<Aquamarine::SOutputMode> m_currentMode;
|
||||
SP<Aquamarine::CSwapchain> m_cursorSwapchain;
|
||||
|
|
@ -302,7 +303,6 @@ class CMonitor {
|
|||
void setSpecialWorkspace(const WORKSPACEID& id);
|
||||
void moveTo(const Vector2D& pos);
|
||||
Vector2D middle();
|
||||
void updateMatrix();
|
||||
WORKSPACEID activeWorkspaceID();
|
||||
WORKSPACEID activeSpecialWorkspaceID();
|
||||
CBox logicalBox();
|
||||
|
|
@ -332,6 +332,11 @@ class CMonitor {
|
|||
bool wantsHDR();
|
||||
|
||||
bool inHDR();
|
||||
bool gammaRampsInUse();
|
||||
|
||||
//
|
||||
const Mat3x3& getTransformMatrix();
|
||||
const Mat3x3& getScaleMatrix();
|
||||
|
||||
/// Has an active workspace with a real fullscreen window (includes special workspace)
|
||||
bool inFullscreenMode();
|
||||
|
|
@ -353,8 +358,8 @@ class CMonitor {
|
|||
PHLWINDOWREF m_previousFSWindow;
|
||||
bool m_needsHDRupdate = false;
|
||||
|
||||
NColorManagement::PImageDescription m_imageDescription;
|
||||
bool m_noShaderCTM = false; // sets drm CTM, restore needed
|
||||
NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});
|
||||
bool m_noShaderCTM = false; // sets drm CTM, restore needed
|
||||
|
||||
// For the list lookup
|
||||
|
||||
|
|
@ -362,12 +367,19 @@ class CMonitor {
|
|||
return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;
|
||||
}
|
||||
|
||||
Mat3x3 m_projMatrix;
|
||||
|
||||
private:
|
||||
void updateMatrix();
|
||||
Mat3x3 m_projOutputMatrix;
|
||||
|
||||
void setupDefaultWS(const SMonitorRule&);
|
||||
WORKSPACEID findAvailableDefaultWS();
|
||||
void commitDPMSState(bool state);
|
||||
void updateVCGTRamps();
|
||||
|
||||
bool m_doneScheduled = false;
|
||||
bool m_vcgtRampsSet = false;
|
||||
std::stack<WORKSPACEID> m_prevWorkSpaces;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ std::string NTransferFunction::toString(eTF tf) {
|
|||
return "";
|
||||
}
|
||||
|
||||
eTF NTransferFunction::fromConfig() {
|
||||
eTF NTransferFunction::fromConfig(bool useICC) {
|
||||
if (useICC)
|
||||
return TF_SRGB;
|
||||
|
||||
static auto PSDREOTF = CConfigValue<Hyprlang::STRING>("render:cm_sdr_eotf");
|
||||
static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF);
|
||||
static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); });
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ namespace NTransferFunction {
|
|||
eTF fromString(const std::string tfName);
|
||||
std::string toString(eTF tf);
|
||||
|
||||
eTF fromConfig();
|
||||
eTF fromConfig(bool useICC = false);
|
||||
}
|
||||
|
|
|
|||
193
src/helpers/cm/ColorManagement.cpp
Normal file
193
src/helpers/cm/ColorManagement.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -3,6 +3,11 @@
|
|||
#include "color-management-v1.hpp"
|
||||
#include <hyprgraphics/color/Color.hpp>
|
||||
#include "../../helpers/memory/Memory.hpp"
|
||||
#include "../../helpers/math/Math.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <expected>
|
||||
|
||||
#define SDR_MIN_LUMINANCE 0.2
|
||||
#define SDR_MAX_LUMINANCE 80.0
|
||||
|
|
@ -12,6 +17,8 @@
|
|||
#define HDR_REF_LUMINANCE 203.0
|
||||
#define HLG_MAX_LUMINANCE 1000.0
|
||||
|
||||
class ITexture;
|
||||
|
||||
namespace NColorManagement {
|
||||
enum eNoShader : uint8_t {
|
||||
CM_NS_DISABLE = 0,
|
||||
|
|
@ -67,7 +74,6 @@ namespace NColorManagement {
|
|||
using SPCPRimaries = Hyprgraphics::SPCPRimaries;
|
||||
|
||||
namespace NColorPrimaries {
|
||||
static const auto DEFAULT_PRIMARIES = SPCPRimaries{};
|
||||
|
||||
static const auto BT709 = SPCPRimaries{
|
||||
.red = {.x = 0.64, .y = 0.33},
|
||||
|
|
@ -76,6 +82,8 @@ namespace NColorManagement {
|
|||
.white = {.x = 0.3127, .y = 0.3290},
|
||||
};
|
||||
|
||||
static const auto DEFAULT_PRIMARIES = BT709;
|
||||
|
||||
static const auto PAL_M = SPCPRimaries{
|
||||
.red = {.x = 0.67, .y = 0.33},
|
||||
.green = {.x = 0.21, .y = 0.71},
|
||||
|
|
@ -140,7 +148,16 @@ namespace NColorManagement {
|
|||
};
|
||||
}
|
||||
|
||||
const SPCPRimaries& getPrimaries(ePrimaries name);
|
||||
struct SVCGTTable16 {
|
||||
uint16_t channels = 0;
|
||||
uint16_t entries = 0;
|
||||
uint16_t entrySize = 0;
|
||||
std::array<std::vector<uint16_t>, 3> ch;
|
||||
};
|
||||
|
||||
const SPCPRimaries& getPrimaries(ePrimaries name);
|
||||
std::optional<Mat3x3> rgbToXYZFromPrimaries(SPCPRimaries pr);
|
||||
Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW);
|
||||
|
||||
class CPrimaries {
|
||||
public:
|
||||
|
|
@ -163,22 +180,17 @@ namespace NColorManagement {
|
|||
};
|
||||
|
||||
struct SImageDescription {
|
||||
struct SIccFile {
|
||||
int fd = -1;
|
||||
uint32_t length = 0;
|
||||
uint32_t offset = 0;
|
||||
bool operator==(const SIccFile& i2) const {
|
||||
return fd == i2.fd;
|
||||
}
|
||||
} icc;
|
||||
static std::expected<SImageDescription, std::string> fromICC(const std::filesystem::path& file);
|
||||
|
||||
bool windowsScRGB = false;
|
||||
//
|
||||
std::vector<uint8_t> rawICC;
|
||||
|
||||
eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22;
|
||||
float transferFunctionPower = 1.0f;
|
||||
eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22;
|
||||
float transferFunctionPower = 1.0f;
|
||||
bool windowsScRGB = false;
|
||||
|
||||
bool primariesNameSet = false;
|
||||
ePrimaries primariesNamed = CM_PRIMARIES_SRGB;
|
||||
bool primariesNameSet = false;
|
||||
ePrimaries primariesNamed = CM_PRIMARIES_SRGB;
|
||||
// primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0)
|
||||
// wayland protocol expects int32_t values multiplied by 1000000
|
||||
// drm expects uint16_t values multiplied by 50000
|
||||
|
|
@ -202,11 +214,23 @@ namespace NColorManagement {
|
|||
}
|
||||
} masteringLuminances;
|
||||
|
||||
// Matrix data from ICC
|
||||
struct SICCData {
|
||||
bool present = false;
|
||||
size_t lutSize = 33;
|
||||
std::vector<float> lutDataPacked;
|
||||
SP<ITexture> lutTexture;
|
||||
std::optional<SVCGTTable16> vcgt;
|
||||
} icc;
|
||||
|
||||
uint32_t maxCLL = 0;
|
||||
uint32_t maxFALL = 0;
|
||||
|
||||
bool operator==(const SImageDescription& d2) const {
|
||||
return icc == d2.icc && windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower &&
|
||||
if (icc.present || d2.icc.present)
|
||||
return false;
|
||||
|
||||
return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower &&
|
||||
(primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) &&
|
||||
masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL &&
|
||||
maxFALL == d2.maxFALL;
|
||||
|
|
@ -280,44 +304,48 @@ namespace NColorManagement {
|
|||
class CImageDescription {
|
||||
public:
|
||||
static WP<const CImageDescription> from(const SImageDescription& imageDescription);
|
||||
static WP<const CImageDescription> from(const uint imageDescriptionId);
|
||||
static WP<const CImageDescription> from(const uint32_t imageDescriptionId);
|
||||
|
||||
WP<const CImageDescription> with(const SImageDescription::SPCLuminances& luminances) const;
|
||||
|
||||
const SImageDescription& value() const;
|
||||
uint id() const;
|
||||
uint32_t id() const;
|
||||
|
||||
WP<const CPrimaries> getPrimaries() const;
|
||||
|
||||
private:
|
||||
CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId);
|
||||
uint m_id;
|
||||
uint m_primariesId;
|
||||
uint32_t m_id = 0;
|
||||
uint32_t m_primariesId = 0;
|
||||
SImageDescription m_imageDescription;
|
||||
};
|
||||
|
||||
using PImageDescription = WP<const CImageDescription>;
|
||||
|
||||
static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB),
|
||||
.luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}});
|
||||
static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
|
||||
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB),
|
||||
.luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80},
|
||||
});
|
||||
|
||||
static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
|
||||
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203},
|
||||
});
|
||||
|
||||
static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}});
|
||||
;
|
||||
static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{
|
||||
.windowsScRGB = true,
|
||||
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR,
|
||||
.windowsScRGB = true,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB,
|
||||
.primaries = NColorPrimaries::BT709,
|
||||
.luminances = {.reference = 203},
|
||||
});
|
||||
;
|
||||
|
||||
}
|
||||
static const auto LINEAR_IMAGE_DESCRIPTION = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different?
|
||||
}
|
||||
278
src/helpers/cm/ICC.cpp
Normal file
278
src/helpers/cm/ICC.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -30,8 +30,6 @@ CHyprError::CHyprError() {
|
|||
if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged)
|
||||
g_pHyprRenderer->damageBox(m_damageBox);
|
||||
});
|
||||
|
||||
m_texture = makeShared<CTexture>();
|
||||
}
|
||||
|
||||
void CHyprError::queueCreate(std::string message, const CHyprColor& color) {
|
||||
|
|
@ -40,8 +38,8 @@ void CHyprError::queueCreate(std::string message, const CHyprColor& color) {
|
|||
}
|
||||
|
||||
void CHyprError::createQueued() {
|
||||
if (m_isCreated)
|
||||
m_texture->destroyTexture();
|
||||
if (m_isCreated && m_texture)
|
||||
m_texture.reset();
|
||||
|
||||
m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn"));
|
||||
|
||||
|
|
@ -145,12 +143,13 @@ void CHyprError::createQueued() {
|
|||
|
||||
// copy the data to an OpenGL texture we have
|
||||
const auto DATA = cairo_image_surface_get_data(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);
|
||||
auto tex = texture();
|
||||
tex->allocate(PMONITOR->m_pixelSize);
|
||||
tex->bind();
|
||||
tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
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);
|
||||
|
||||
|
|
@ -187,7 +186,8 @@ void CHyprError::draw() {
|
|||
if (!m_fadeOpacity->isBeingAnimated()) {
|
||||
if (m_fadeOpacity->value() == 0.f) {
|
||||
m_queuedDestroy = false;
|
||||
m_texture->destroyTexture();
|
||||
if (m_texture)
|
||||
m_texture.reset();
|
||||
m_isCreated = false;
|
||||
m_queued = "";
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ void CHyprError::draw() {
|
|||
m_monitorChanged = false;
|
||||
|
||||
CTexPassElement::SRenderData data;
|
||||
data.tex = m_texture;
|
||||
data.tex = texture();
|
||||
data.box = monbox;
|
||||
data.a = m_fadeOpacity->value();
|
||||
|
||||
|
|
@ -239,3 +239,9 @@ bool CHyprError::active() {
|
|||
float CHyprError::height() {
|
||||
return m_lastHeight;
|
||||
}
|
||||
|
||||
SP<ITexture> CHyprError::texture() {
|
||||
if (!m_texture)
|
||||
m_texture = g_pHyprRenderer->createTexture();
|
||||
return m_texture;
|
||||
}
|
||||
|
|
@ -18,13 +18,16 @@ class CHyprError {
|
|||
bool active();
|
||||
float height(); // logical
|
||||
|
||||
//
|
||||
SP<ITexture> texture();
|
||||
|
||||
private:
|
||||
void createQueued();
|
||||
std::string m_queued = "";
|
||||
CHyprColor m_queuedColor;
|
||||
bool m_queuedDestroy = false;
|
||||
bool m_isCreated = false;
|
||||
SP<CTexture> m_texture;
|
||||
SP<ITexture> m_texture;
|
||||
PHLANIMVAR<float> m_fadeOpacity;
|
||||
CBox m_damageBox = {0, 0, 0, 0};
|
||||
float m_lastHeight = 0.F;
|
||||
|
|
|
|||
|
|
@ -1605,6 +1605,7 @@ 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_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_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)");
|
||||
|
|
|
|||
|
|
@ -11,13 +11,7 @@
|
|||
|
||||
using namespace Layout;
|
||||
|
||||
CLayoutManager::CLayoutManager() {
|
||||
static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([] {
|
||||
for (const auto& ws : g_pCompositor->getWorkspaces()) {
|
||||
ws->m_space->recheckWorkArea();
|
||||
}
|
||||
});
|
||||
}
|
||||
CLayoutManager::CLayoutManager() = default;
|
||||
|
||||
void CLayoutManager::newTarget(SP<ITarget> target, SP<CSpace> space) {
|
||||
// on a new target: remember desired pos for float, if available
|
||||
|
|
@ -67,6 +61,13 @@ void CLayoutManager::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectC
|
|||
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) {
|
||||
|
||||
const auto MONITOR = Desktop::focusState()->monitor();
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ namespace Layout {
|
|||
void moveMouse(const Vector2D& mousePos);
|
||||
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
void moveTarget(const Vector2D& Δ, SP<ITarget> target);
|
||||
void setTargetGeom(const CBox& box, SP<ITarget> target); // floats only
|
||||
void endDragTarget();
|
||||
|
||||
std::expected<void, std::string> layoutMsg(const std::string_view& sv);
|
||||
|
|
|
|||
|
|
@ -42,16 +42,16 @@ void CAlgorithm::removeTarget(SP<ITarget> target) {
|
|||
const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target);
|
||||
|
||||
if (IS_FLOATING) {
|
||||
m_floating->removeTarget(target);
|
||||
std::erase(m_floatingTargets, target);
|
||||
m_floating->removeTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool IS_TILED = std::ranges::contains(m_tiledTargets, target);
|
||||
|
||||
if (IS_TILED) {
|
||||
m_tiled->removeTarget(target);
|
||||
std::erase(m_tiledTargets, target);
|
||||
m_tiled->removeTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -262,3 +262,10 @@ SP<ITarget> CAlgorithm::getNextCandidate(SP<ITarget> old) {
|
|||
// god damn it, maybe empty?
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ namespace Layout {
|
|||
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
void moveTarget(const Vector2D& Δ, SP<ITarget> target);
|
||||
|
||||
void setTargetGeom(const CBox& box, SP<ITarget> target); // only for float
|
||||
|
||||
void updateFloatingAlgo(UP<IFloatingAlgorithm>&& algo);
|
||||
void updateTiledAlgo(UP<ITiledAlgorithm>&& algo);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace Layout {
|
|||
// a target is being moved by a delta
|
||||
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 recalculate();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
#include "ModeAlgorithm.hpp"
|
||||
|
||||
#include "../space/Space.hpp"
|
||||
#include "Algorithm.hpp"
|
||||
#include "../../helpers/Monitor.hpp"
|
||||
#include "../../desktop/view/Window.hpp"
|
||||
|
||||
using namespace Layout;
|
||||
|
||||
std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_view& sv) {
|
||||
|
|
@ -9,3 +14,20 @@ std::expected<void, std::string> IModeAlgorithm::layoutMsg(const std::string_vie
|
|||
std::optional<Vector2D> IModeAlgorithm::predictSizeForNewTarget() {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ namespace Layout {
|
|||
// optional: predict new window's size
|
||||
virtual std::optional<Vector2D> predictSizeForNewTarget();
|
||||
|
||||
// Impl'd here: focal point for dir
|
||||
virtual std::optional<Vector2D> focalPointForDir(SP<ITarget> t, Math::eDirection dir);
|
||||
|
||||
protected:
|
||||
IModeAlgorithm() = default;
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) {
|
|||
PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize;
|
||||
}
|
||||
}
|
||||
|
||||
updateTarget(target);
|
||||
}
|
||||
|
||||
void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
|
||||
|
|
@ -152,6 +154,8 @@ void CDefaultFloatingAlgorithm::movedTarget(SP<ITarget> target, std::optional<Ve
|
|||
// put around the current center, fit in workArea
|
||||
target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target));
|
||||
}
|
||||
|
||||
updateTarget(target);
|
||||
}
|
||||
|
||||
CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t) {
|
||||
|
|
@ -173,6 +177,7 @@ CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP<ITarget> t)
|
|||
|
||||
void CDefaultFloatingAlgorithm::removeTarget(SP<ITarget> target) {
|
||||
target->rememberFloatingSize(target->position().size());
|
||||
m_datas.erase(target);
|
||||
}
|
||||
|
||||
void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
|
||||
|
|
@ -184,6 +189,8 @@ void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> tar
|
|||
|
||||
if (g_layoutManager->dragController()->target() == target)
|
||||
target->warpPositionSize();
|
||||
|
||||
updateTarget(target);
|
||||
}
|
||||
|
||||
void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> target) {
|
||||
|
|
@ -193,12 +200,17 @@ void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP<ITarget> targe
|
|||
|
||||
if (g_layoutManager->dragController()->target() == target)
|
||||
target->warpPositionSize();
|
||||
|
||||
updateTarget(target);
|
||||
}
|
||||
|
||||
void CDefaultFloatingAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
||||
auto posABackup = a->position();
|
||||
a->setPositionGlobal(b->position());
|
||||
b->setPositionGlobal(posABackup);
|
||||
|
||||
updateTarget(a);
|
||||
updateTarget(b);
|
||||
}
|
||||
|
||||
void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
||||
|
|
@ -216,4 +228,25 @@ void CDefaultFloatingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDire
|
|||
}
|
||||
|
||||
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()};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "../../FloatingAlgorithm.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace Layout {
|
||||
class CAlgorithm;
|
||||
}
|
||||
|
|
@ -17,10 +19,22 @@ namespace Layout::Floating {
|
|||
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
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 moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
|
||||
virtual void recenter(SP<ITarget> t);
|
||||
|
||||
private:
|
||||
CBox fitBoxInWorkArea(const CBox& box, SP<ITarget> t);
|
||||
|
||||
void updateTarget(SP<ITarget>);
|
||||
|
||||
struct SWindowData {
|
||||
CBox lastBox;
|
||||
};
|
||||
|
||||
std::map<WP<ITarget>, SWindowData> m_datas;
|
||||
};
|
||||
};
|
||||
|
|
@ -99,11 +99,13 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
|
|||
if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
|
||||
OPENINGON = getClosestNode(MOUSECOORDS);
|
||||
|
||||
} else if (*PUSEACTIVE) {
|
||||
} else if (*PUSEACTIVE || m_overrideFocalPoint) {
|
||||
const auto ACTIVE_WINDOW = Desktop::focusState()->window();
|
||||
|
||||
if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE &&
|
||||
ACTIVE_WINDOW->m_isMapped)
|
||||
if (m_overrideFocalPoint)
|
||||
OPENINGON = getClosestNode(*m_overrideFocalPoint);
|
||||
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);
|
||||
|
||||
if (!OPENINGON)
|
||||
|
|
@ -182,7 +184,7 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
|
|||
// whether or not the override persists after opening one window
|
||||
if (*PERMANENTDIRECTIONOVERRIDE == 0)
|
||||
m_overrideDirection = Math::DIRECTION_DEFAULT;
|
||||
} else if (*PSMARTSPLIT == 1) {
|
||||
} else if (*PSMARTSPLIT == 1 || m_overrideFocalPoint) {
|
||||
const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;
|
||||
const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;
|
||||
const auto DELTA = MOUSECOORDS - PARENT_CENTER;
|
||||
|
|
@ -214,10 +216,7 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
|
|||
}
|
||||
}
|
||||
} else if (*PFORCESPLIT == 0 || !newTarget) {
|
||||
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))) {
|
||||
if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) {
|
||||
// we are hovering over the first node, make PNODE first.
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
|
|
@ -242,11 +241,10 @@ void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
|
|||
|
||||
// and update the previous parent if it exists
|
||||
if (OPENINGON->pParent) {
|
||||
if (OPENINGON->pParent->children[0] == OPENINGON) {
|
||||
if (OPENINGON->pParent->children[0] == OPENINGON)
|
||||
OPENINGON->pParent->children[0] = NEWPARENT;
|
||||
} else {
|
||||
else
|
||||
OPENINGON->pParent->children[1] = NEWPARENT;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the children
|
||||
|
|
@ -551,41 +549,35 @@ std::optional<Vector2D> CDwindleAlgorithm::predictSizeForNewTarget() {
|
|||
}
|
||||
|
||||
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 Vector2D originalPos = t->position().middle();
|
||||
|
||||
if (!PNODE || !t->window())
|
||||
return;
|
||||
|
||||
Vector2D focalPoint;
|
||||
const auto FOCAL_POINT = focalPointForDir(t, dir);
|
||||
|
||||
const auto WINDOWIDEALBB =
|
||||
t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved();
|
||||
const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle()));
|
||||
|
||||
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;
|
||||
}
|
||||
if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK)
|
||||
return; // noop
|
||||
|
||||
t->window()->setAnimationsToMove();
|
||||
|
||||
removeTarget(t);
|
||||
|
||||
const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint);
|
||||
|
||||
if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) {
|
||||
// move with a focal point
|
||||
|
||||
if (PMONITORFOCAL->m_activeWorkspace)
|
||||
t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space);
|
||||
t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
movedTarget(t, focalPoint);
|
||||
movedTarget(t, FOCAL_POINT);
|
||||
|
||||
// restore focus to the previous position
|
||||
if (silent) {
|
||||
|
|
@ -665,11 +657,28 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
|
|||
const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window());
|
||||
|
||||
if (ARGS[0] == "togglesplit") {
|
||||
if (CURRENT_NODE)
|
||||
toggleSplit(CURRENT_NODE);
|
||||
if (CURRENT_NODE) {
|
||||
if (!toggleSplit(CURRENT_NODE))
|
||||
return std::unexpected("can't togglesplit in the current workspace");
|
||||
}
|
||||
} else if (ARGS[0] == "swapsplit") {
|
||||
if (CURRENT_NODE)
|
||||
swapSplit(CURRENT_NODE);
|
||||
if (CURRENT_NODE) {
|
||||
if (!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") {
|
||||
auto node = CURRENT_NODE;
|
||||
if (!ARGS[1].empty()) {
|
||||
|
|
@ -679,7 +688,8 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
|
|||
}
|
||||
|
||||
const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable";
|
||||
moveToRoot(node, STABLE);
|
||||
if (!moveToRoot(node, STABLE))
|
||||
return std::unexpected("can't movetoroot in the current workspace");
|
||||
} else if (ARGS[0] == "preselect") {
|
||||
auto direction = ARGS[1];
|
||||
|
||||
|
|
@ -714,42 +724,102 @@ std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_
|
|||
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 {};
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {
|
||||
bool CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {
|
||||
if (!x || !x->pParent)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
return;
|
||||
return false;
|
||||
|
||||
x->pParent->splitTop = !x->pParent->splitTop;
|
||||
|
||||
x->pParent->recalcSizePosRecursive();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
return;
|
||||
bool CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent)
|
||||
return false;
|
||||
|
||||
std::swap(x->pParent->children[0], x->pParent->children[1]);
|
||||
|
||||
x->pParent->recalcSizePosRecursive();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
|
||||
void CDwindleAlgorithm::rotateSplit(SP<SDwindleNodeData> x, int angle) {
|
||||
if (!x || !x->pParent)
|
||||
return;
|
||||
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
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
|
||||
if (!x->pParent->pParent)
|
||||
return;
|
||||
return false;
|
||||
|
||||
auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1];
|
||||
|
||||
|
|
@ -770,4 +840,6 @@ void CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
|
|||
std::swap(pRoot->children[0], pRoot->children[1]);
|
||||
|
||||
pRoot->recalcSizePosRecursive();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,10 @@ namespace Layout::Tiled {
|
|||
SP<SDwindleNodeData> getClosestNode(const Vector2D&, SP<ITarget> skip = nullptr);
|
||||
SP<SDwindleNodeData> getMasterNode();
|
||||
|
||||
void toggleSplit(SP<SDwindleNodeData>);
|
||||
void swapSplit(SP<SDwindleNodeData>);
|
||||
void moveToRoot(SP<SDwindleNodeData>, bool stable = true);
|
||||
bool toggleSplit(SP<SDwindleNodeData>);
|
||||
bool swapSplit(SP<SDwindleNodeData>);
|
||||
void rotateSplit(SP<SDwindleNodeData>, int angle = 90);
|
||||
bool moveToRoot(SP<SDwindleNodeData>, bool stable = true);
|
||||
|
||||
Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -403,7 +403,9 @@ void CMasterAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
|||
}
|
||||
|
||||
void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
||||
const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);
|
||||
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
|
||||
|
||||
const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir);
|
||||
|
||||
if (!t->window())
|
||||
return;
|
||||
|
|
@ -424,7 +426,10 @@ void CMasterAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir
|
|||
t->window()->setAnimationsToMove();
|
||||
|
||||
if (t->window()->m_workspace != targetWs) {
|
||||
t->assignToSpace(targetWs->m_space);
|
||||
if (!*PMONITORFALLBACK)
|
||||
return; // noop
|
||||
|
||||
t->assignToSpace(targetWs->m_space, focalPointForDir(t, dir));
|
||||
} else if (PWINDOW2) {
|
||||
// if same monitor, switch windows
|
||||
g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget());
|
||||
|
|
@ -720,8 +725,8 @@ std::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_v
|
|||
|
||||
for (auto& nd : m_masterNodesData) {
|
||||
if (!nd->isMaster) {
|
||||
const auto newMaster = nd;
|
||||
newMaster->isMaster = true;
|
||||
const auto& newMaster = nd;
|
||||
newMaster->isMaster = true;
|
||||
|
||||
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
|
||||
|
||||
|
|
@ -756,8 +761,8 @@ std::expected<void, std::string> CMasterAlgorithm::layoutMsg(const std::string_v
|
|||
|
||||
for (auto& nd : m_masterNodesData | std::views::reverse) {
|
||||
if (!nd->isMaster) {
|
||||
const auto newMaster = nd;
|
||||
newMaster->isMaster = true;
|
||||
const auto& newMaster = nd;
|
||||
newMaster->isMaster = true;
|
||||
|
||||
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
|
||||
|
||||
|
|
@ -956,7 +961,9 @@ void CMasterAlgorithm::calculateWorkspace() {
|
|||
const auto STACKWINDOWS = WINDOWS - MASTERS;
|
||||
const auto WORKAREA = m_parent->space()->workArea();
|
||||
const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
|
||||
const auto UNRESERVED_WIDTH = WORKAREA.width + PMONITOR->m_reservedArea.left() + PMONITOR->m_reservedArea.right();
|
||||
const auto reservedLeft = PMONITOR ? PMONITOR->m_reservedArea.left() : 0;
|
||||
const auto reservedRight = PMONITOR ? PMONITOR->m_reservedArea.right() : 0;
|
||||
const auto UNRESERVED_WIDTH = WORKAREA.width + reservedLeft + reservedRight;
|
||||
|
||||
if (orientation == ORIENTATION_CENTER) {
|
||||
if (STACKWINDOWS >= *SLAVECOUNTFORCENTER)
|
||||
|
|
@ -1074,7 +1081,7 @@ void CMasterAlgorithm::calculateWorkspace() {
|
|||
}
|
||||
|
||||
nd->size = Vector2D(WIDTH, HEIGHT);
|
||||
nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(PMONITOR->m_reservedArea.left(), 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);
|
||||
nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(reservedLeft, 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY);
|
||||
nd->pTarget->setPositionGlobal({nd->position, nd->size});
|
||||
|
||||
mastersLeft--;
|
||||
|
|
@ -1192,7 +1199,7 @@ void CMasterAlgorithm::calculateWorkspace() {
|
|||
continue;
|
||||
|
||||
if (onRight) {
|
||||
nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? PMONITOR->m_reservedArea.left() : 0);
|
||||
nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? reservedLeft : 0);
|
||||
nextY = nextYR;
|
||||
heightLeft = heightLeftR;
|
||||
slavesLeft = slavesLeftR;
|
||||
|
|
@ -1217,7 +1224,7 @@ void CMasterAlgorithm::calculateWorkspace() {
|
|||
}
|
||||
}
|
||||
|
||||
nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedArea.right() : PMONITOR->m_reservedArea.left())) : WIDTH, HEIGHT);
|
||||
nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? reservedRight : reservedLeft)) : WIDTH, HEIGHT);
|
||||
nd->position = WORKAREA.pos() + Vector2D(nextX, nextY);
|
||||
nd->pTarget->setPositionGlobal({nd->position, nd->size});
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@ void CMonocleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
|||
}
|
||||
|
||||
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
|
||||
if (!t || !t->space() || !t->space()->workspace())
|
||||
return;
|
||||
|
|
@ -215,7 +220,7 @@ void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection di
|
|||
if (t->window())
|
||||
t->window()->setAnimationsToMove();
|
||||
|
||||
t->assignToSpace(TARGETWS->m_space);
|
||||
t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,12 +55,14 @@ size_t CScrollTapeController::addStrip(float size) {
|
|||
return m_strips.size() - 1;
|
||||
}
|
||||
|
||||
void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
|
||||
if (afterIndex >= m_strips.size()) {
|
||||
void CScrollTapeController::insertStrip(ssize_t afterIndex, float size) {
|
||||
if (afterIndex >= sc<ssize_t>(m_strips.size())) {
|
||||
addStrip(size);
|
||||
return;
|
||||
}
|
||||
|
||||
afterIndex = std::clamp(afterIndex, sc<ssize_t>(-1L), sc<ssize_t>(INT32_MAX));
|
||||
|
||||
SStripData newStrip;
|
||||
newStrip.size = size;
|
||||
m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Layout::Tiled {
|
|||
bool isReversed() const;
|
||||
|
||||
size_t addStrip(float size = 1.0F);
|
||||
void insertStrip(size_t afterIndex, float size = 1.0F);
|
||||
void insertStrip(ssize_t afterIndex, float size = 1.0F);
|
||||
void removeStrip(size_t index);
|
||||
size_t stripCount() const;
|
||||
SStripData& getStrip(size_t index);
|
||||
|
|
|
|||
|
|
@ -190,10 +190,12 @@ size_t SColumnData::idx(SP<ITarget> t) {
|
|||
}
|
||||
|
||||
size_t SColumnData::idxForHeight(float y) {
|
||||
if (targetDatas.empty())
|
||||
return 0;
|
||||
for (size_t i = 0; i < targetDatas.size(); ++i) {
|
||||
if (targetDatas[i]->target->position().y < y)
|
||||
continue;
|
||||
return i - 1;
|
||||
return i == 0 ? 0 : i - 1;
|
||||
}
|
||||
return targetDatas.size() - 1;
|
||||
}
|
||||
|
|
@ -245,24 +247,28 @@ void SColumnData::remove(SP<ITarget> t) {
|
|||
scrollingData->remove(self.lock());
|
||||
}
|
||||
|
||||
void SColumnData::up(SP<SScrollingTargetData> w) {
|
||||
bool SColumnData::up(SP<SScrollingTargetData> w) {
|
||||
for (size_t i = 1; i < targetDatas.size(); ++i) {
|
||||
if (targetDatas[i] != w)
|
||||
continue;
|
||||
|
||||
std::swap(targetDatas[i], targetDatas[i - 1]);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SColumnData::down(SP<SScrollingTargetData> w) {
|
||||
bool SColumnData::down(SP<SScrollingTargetData> w) {
|
||||
for (size_t i = 0; i < targetDatas.size() - 1; ++i) {
|
||||
if (targetDatas[i] != w)
|
||||
continue;
|
||||
|
||||
std::swap(targetDatas[i], targetDatas[i + 1]);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SP<SScrollingTargetData> SColumnData::next(SP<SScrollingTargetData> w) {
|
||||
|
|
@ -296,23 +302,21 @@ SScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) {
|
|||
}
|
||||
|
||||
SP<SColumnData> SScrollingData::add() {
|
||||
static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
|
||||
auto col = columns.emplace_back(makeShared<SColumnData>(self.lock()));
|
||||
col->self = col;
|
||||
auto col = columns.emplace_back(makeShared<SColumnData>(self.lock()));
|
||||
col->self = col;
|
||||
|
||||
size_t stripIdx = controller->addStrip(*PCOLWIDTH);
|
||||
size_t stripIdx = controller->addStrip(algorithm->defaultColumnWidth());
|
||||
controller->getStrip(stripIdx).userData = col;
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
SP<SColumnData> SScrollingData::add(int after) {
|
||||
static const auto PCOLWIDTH = CConfigValue<Hyprlang::FLOAT>("scrolling:column_width");
|
||||
auto col = makeShared<SColumnData>(self.lock());
|
||||
col->self = col;
|
||||
auto col = makeShared<SColumnData>(self.lock());
|
||||
col->self = col;
|
||||
columns.insert(columns.begin() + after + 1, col);
|
||||
|
||||
controller->insertStrip(after, *PCOLWIDTH);
|
||||
controller->insertStrip(after, algorithm->defaultColumnWidth());
|
||||
controller->getStrip(after + 1).userData = col;
|
||||
|
||||
return col;
|
||||
|
|
@ -466,6 +470,21 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
|
|||
m_scrollingData = makeShared<SScrollingData>(this);
|
||||
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
|
||||
auto parseDirection = [](const std::string& dir) -> eScrollDirection {
|
||||
if (dir == "left")
|
||||
|
|
@ -478,19 +497,11 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
|
|||
return SCROLL_DIR_RIGHT; // default
|
||||
};
|
||||
|
||||
m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseDirection] {
|
||||
m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseColumnWidths, parseDirection] {
|
||||
static const auto PCONFDIRECTION = CConfigValue<Hyprlang::STRING>("scrolling:direction");
|
||||
|
||||
m_config.configuredWidths.clear();
|
||||
|
||||
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};
|
||||
m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS);
|
||||
|
||||
// Update scroll direction
|
||||
m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
|
||||
|
|
@ -523,7 +534,7 @@ CScrollingAlgorithm::CScrollingAlgorithm() {
|
|||
});
|
||||
|
||||
// Initialize default widths and direction
|
||||
m_config.configuredWidths = {0.333, 0.5, 0.667, 1.0};
|
||||
m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS);
|
||||
m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION));
|
||||
}
|
||||
|
||||
|
|
@ -616,16 +627,20 @@ void CScrollingAlgorithm::removeTarget(SP<ITarget> target) {
|
|||
|
||||
if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) {
|
||||
// move the view if this is the last column
|
||||
const auto USABLE = usableArea();
|
||||
m_scrollingData->controller->adjustOffset(-(USABLE.w * DATA->column->getColumnWidth()));
|
||||
const auto USABLE = usableArea();
|
||||
const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();
|
||||
const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h;
|
||||
m_scrollingData->controller->adjustOffset(-(usablePrimary * DATA->column->getColumnWidth()));
|
||||
}
|
||||
|
||||
DATA->column->remove(target);
|
||||
|
||||
if (!DATA->column) {
|
||||
// column got removed, let's ensure we don't leave any cringe extra space
|
||||
const auto USABLE = usableArea();
|
||||
double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - USABLE.w, 1.0));
|
||||
const auto USABLE = usableArea();
|
||||
const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -833,66 +848,129 @@ void CScrollingAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection
|
|||
}
|
||||
|
||||
void CScrollingAlgorithm::moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
||||
const auto DATA = dataFor(t);
|
||||
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
|
||||
|
||||
const auto DATA = dataFor(t);
|
||||
|
||||
if (!DATA)
|
||||
return;
|
||||
|
||||
const auto TAPE_DIR = getDynamicDirection();
|
||||
const auto CURRENT_COL = DATA->column.lock();
|
||||
const auto current_idx = m_scrollingData->idx(CURRENT_COL);
|
||||
|
||||
if (dir == Math::DIRECTION_LEFT) {
|
||||
const auto COL = m_scrollingData->prev(DATA->column.lock());
|
||||
auto rotateDir = [this](Math::eDirection dir) -> Math::eDirection {
|
||||
switch (m_scrollingData->controller->getDirection()) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
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);
|
||||
return dir;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
} else if (dir == Math::DIRECTION_UP)
|
||||
DATA->column->up(DATA);
|
||||
else if (dir == Math::DIRECTION_DOWN)
|
||||
DATA->column->down(DATA);
|
||||
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();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_scrollingData->recalculate();
|
||||
focusTargetUpdate(t);
|
||||
if (t->window())
|
||||
g_pCompositor->warpCursorTo(t->window()->middle());
|
||||
}
|
||||
|
||||
std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::string_view& sv) {
|
||||
|
|
@ -1177,8 +1255,9 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
|
|||
m_scrollingData->recalculate();
|
||||
}
|
||||
} else if (ARGS[0] == "focus") {
|
||||
const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr);
|
||||
static const auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
|
||||
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 PCONFWRAPFOCUS = CConfigValue<Hyprlang::INT>("scrolling:wrap_focus");
|
||||
|
||||
if (!TDATA || ARGS[1].empty())
|
||||
return std::unexpected("no window to focus");
|
||||
|
|
@ -1234,7 +1313,7 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
|
|||
g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
|
||||
return {};
|
||||
} else
|
||||
PREV = m_scrollingData->columns.back();
|
||||
PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front();
|
||||
}
|
||||
|
||||
auto pTargetData = findBestNeighbor(TDATA, PREV);
|
||||
|
|
@ -1256,7 +1335,7 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
|
|||
g_pCompositor->warpCursorTo(TDATA->target->window()->middle());
|
||||
return {};
|
||||
} else
|
||||
NEXT = m_scrollingData->columns.front();
|
||||
NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back();
|
||||
}
|
||||
|
||||
auto pTargetData = findBestNeighbor(TDATA, NEXT);
|
||||
|
|
@ -1283,6 +1362,8 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
|
|||
|
||||
m_scrollingData->recalculate();
|
||||
} else if (ARGS[0] == "swapcol") {
|
||||
static const auto PCONFWRAPSWAPCOL = CConfigValue<Hyprlang::INT>("scrolling:wrap_swapcol");
|
||||
|
||||
if (ARGS.size() < 2)
|
||||
return std::unexpected("not enough args");
|
||||
|
||||
|
|
@ -1308,9 +1389,15 @@ std::expected<void, std::string> CScrollingAlgorithm::layoutMsg(const std::strin
|
|||
|
||||
// wrap around swaps
|
||||
if (direction == "l")
|
||||
targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);
|
||||
if (*PCONFWRAPSWAPCOL == 1)
|
||||
targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1);
|
||||
else
|
||||
targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1);
|
||||
else if (direction == "r")
|
||||
targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);
|
||||
if (*PCONFWRAPSWAPCOL == 1)
|
||||
targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1);
|
||||
else
|
||||
targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1);
|
||||
else
|
||||
return std::unexpected("no target (invalid direction?)");
|
||||
;
|
||||
|
|
@ -1418,9 +1505,14 @@ CBox CScrollingAlgorithm::usableArea() {
|
|||
CBox box = m_parent->space()->workArea();
|
||||
|
||||
// doesn't matter, this happens when this algo is about to be destroyed
|
||||
if (!m_parent->space()->workspace())
|
||||
if (!m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor)
|
||||
return box;
|
||||
|
||||
box.translate(-m_parent->space()->workspace()->m_monitor->m_position);
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ namespace Layout::Tiled {
|
|||
// index of lowest target that is above y.
|
||||
size_t idxForHeight(float y);
|
||||
|
||||
void up(SP<SScrollingTargetData> w);
|
||||
void down(SP<SScrollingTargetData> w);
|
||||
bool up(SP<SScrollingTargetData> w);
|
||||
bool down(SP<SScrollingTargetData> w);
|
||||
|
||||
SP<SScrollingTargetData> next(SP<SScrollingTargetData> w);
|
||||
SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w);
|
||||
|
|
@ -138,6 +138,8 @@ namespace Layout::Tiled {
|
|||
void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
void focusOnInput(SP<ITarget> target, eInputMode input);
|
||||
|
||||
float defaultColumnWidth();
|
||||
|
||||
friend struct SScrollingData;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "../../debug/log/Logger.hpp"
|
||||
#include "../../desktop/Workspace.hpp"
|
||||
#include "../../config/ConfigManager.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
|
||||
using namespace Layout;
|
||||
|
||||
|
|
@ -17,6 +18,12 @@ SP<CSpace> CSpace::create(PHLWORKSPACE w) {
|
|||
|
||||
CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) {
|
||||
recheckWorkArea();
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
m_geomUpdateCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] {
|
||||
recheckWorkArea();
|
||||
m_algorithm->recalculate();
|
||||
});
|
||||
}
|
||||
|
||||
void CSpace::add(SP<ITarget> t) {
|
||||
|
|
@ -176,6 +183,11 @@ void CSpace::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool sil
|
|||
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) {
|
||||
return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ namespace Layout {
|
|||
|
||||
void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
void moveTarget(const Vector2D& Δ, SP<ITarget> target);
|
||||
void setTargetGeom(const CBox& box, SP<ITarget> target); // only for float
|
||||
|
||||
SP<CAlgorithm> algorithm() const;
|
||||
|
||||
|
|
@ -63,5 +64,8 @@ namespace Layout {
|
|||
|
||||
// work area is in global coords
|
||||
CBox m_workArea, m_floatingWorkArea;
|
||||
|
||||
// for recalc
|
||||
CHyprSignalListener m_geomUpdateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -239,6 +239,8 @@ void CDragStateController::dragEnd() {
|
|||
|
||||
draggingTarget->damageEntire();
|
||||
|
||||
g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget);
|
||||
|
||||
Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
|
||||
m_wasDraggingWindow = false;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
#include "../../Compositor.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Hyprutils::Utils;
|
||||
using namespace Layout;
|
||||
|
||||
SP<ITarget> CWindowTarget::create(PHLWINDOW w) {
|
||||
|
|
@ -34,6 +37,9 @@ void CWindowTarget::setPositionGlobal(const CBox& box) {
|
|||
|
||||
void CWindowTarget::updatePos() {
|
||||
|
||||
g_pHyprRenderer->damageWindow(m_window.lock());
|
||||
CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); });
|
||||
|
||||
if (!m_space)
|
||||
return;
|
||||
|
||||
|
|
@ -55,6 +61,11 @@ void CWindowTarget::updatePos() {
|
|||
|
||||
// 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 PWORKSPACE = m_space->workspace();
|
||||
|
||||
|
|
@ -104,7 +115,7 @@ void CWindowTarget::updatePos() {
|
|||
|
||||
Vector2D ratioPadding;
|
||||
|
||||
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1) {
|
||||
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && fullscreenMode() == FSMODE_NONE) {
|
||||
const Vector2D originalSize = MONITOR_WORKAREA.size();
|
||||
|
||||
const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
|
||||
|
|
@ -131,7 +142,7 @@ void CWindowTarget::updatePos() {
|
|||
calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;
|
||||
calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
|
||||
|
||||
if (isPseudo()) {
|
||||
if (isPseudo() && fullscreenMode() == FSMODE_NONE) {
|
||||
// Calculate pseudo
|
||||
float scale = 1;
|
||||
|
||||
|
|
@ -162,18 +173,14 @@ void CWindowTarget::updatePos() {
|
|||
static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
|
||||
|
||||
if (*PCLAMP_TILED) {
|
||||
const auto borderSize = m_window->getRealBorderSize();
|
||||
Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
|
||||
|
||||
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);
|
||||
Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
|
||||
Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY});
|
||||
calcSize = calcSize.clamp(minSize, maxSize);
|
||||
|
||||
calcPos += (availableSpace - calcSize) / 2.0;
|
||||
|
||||
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 + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize);
|
||||
calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x);
|
||||
calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y);
|
||||
}
|
||||
|
||||
if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) {
|
||||
|
|
@ -370,5 +377,6 @@ void CWindowTarget::onUpdateSpace() {
|
|||
m_window->m_monitor = space()->workspace()->m_monitor;
|
||||
m_window->moveToWorkspace(space()->workspace());
|
||||
m_window->updateToplevel();
|
||||
m_window->updateWindowData();
|
||||
m_window->updateWindowDecos();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,9 +109,6 @@ CKeybindManager::CKeybindManager() {
|
|||
m_dispatchers["togglegroup"] = toggleGroup;
|
||||
m_dispatchers["changegroupactive"] = changeGroupActive;
|
||||
m_dispatchers["movegroupwindow"] = moveGroupWindow;
|
||||
m_dispatchers["togglesplit"] = toggleSplit;
|
||||
m_dispatchers["swapsplit"] = swapSplit;
|
||||
m_dispatchers["splitratio"] = alterSplitRatio;
|
||||
m_dispatchers["focusmonitor"] = focusMonitor;
|
||||
m_dispatchers["movecursortocorner"] = moveCursorToCorner;
|
||||
m_dispatchers["movecursor"] = moveCursor;
|
||||
|
|
@ -1468,9 +1465,10 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) {
|
|||
}
|
||||
|
||||
SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
|
||||
static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen");
|
||||
static auto PGROUPCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_groupfirst");
|
||||
Math::eDirection dir = Math::fromChar(args[0]);
|
||||
static auto PFULLCYCLE = CConfigValue<Hyprlang::INT>("binds:movefocus_cycles_fullscreen");
|
||||
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]);
|
||||
|
||||
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]);
|
||||
|
|
@ -1479,7 +1477,8 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
|
|||
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) {
|
||||
tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));
|
||||
if (*PMONITORFALLBACK)
|
||||
tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -1509,7 +1508,7 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) {
|
|||
|
||||
Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir));
|
||||
|
||||
if (tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)))
|
||||
if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)))
|
||||
return {};
|
||||
|
||||
static auto PNOFALLBACK = CConfigValue<Hyprlang::INT>("general:no_focus_fallback");
|
||||
|
|
@ -1690,7 +1689,10 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) {
|
|||
// index starts from '1'; '0' means last window
|
||||
try {
|
||||
const int INDEX = std::stoi(args);
|
||||
PWINDOW->m_group->setCurrent(INDEX);
|
||||
if (INDEX <= 0)
|
||||
PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1);
|
||||
else
|
||||
PWINDOW->m_group->setCurrent(INDEX - 1);
|
||||
} catch (...) { return {.success = false, .error = "invalid idx"}; }
|
||||
|
||||
return {};
|
||||
|
|
@ -1704,18 +1706,6 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) {
|
|||
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) {
|
||||
const auto PMONITOR = g_pCompositor->getMonitorFromString(arg);
|
||||
tryMoveFocusToMonitor(PMONITOR);
|
||||
|
|
@ -2747,7 +2737,9 @@ void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string&
|
|||
|
||||
WP<Desktop::View::CGroup> group = pWindow->m_group;
|
||||
|
||||
pWindow->m_group->remove(pWindow);
|
||||
const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT;
|
||||
|
||||
pWindow->m_group->remove(pWindow, direction);
|
||||
|
||||
if (*BFOCUSREMOVEDWINDOW || !group) {
|
||||
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
|
||||
|
|
|
|||
|
|
@ -194,10 +194,7 @@ class CKeybindManager {
|
|||
static SDispatchResult swapActive(std::string);
|
||||
static SDispatchResult toggleGroup(std::string);
|
||||
static SDispatchResult changeGroupActive(std::string);
|
||||
static SDispatchResult alterSplitRatio(std::string);
|
||||
static SDispatchResult focusMonitor(std::string);
|
||||
static SDispatchResult toggleSplit(std::string);
|
||||
static SDispatchResult swapSplit(std::string);
|
||||
static SDispatchResult moveCursorToCorner(std::string);
|
||||
static SDispatchResult moveCursor(std::string);
|
||||
static SDispatchResult workspaceOpt(std::string);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "../protocols/IdleNotify.hpp"
|
||||
#include "../protocols/core/Compositor.hpp"
|
||||
#include "../protocols/core/Seat.hpp"
|
||||
#include "debug/log/Logger.hpp"
|
||||
#include "eventLoop/EventLoopManager.hpp"
|
||||
#include "../render/pass/TexPassElement.hpp"
|
||||
#include "../managers/input/InputManager.hpp"
|
||||
|
|
@ -18,9 +19,12 @@
|
|||
#include "../helpers/time/Time.hpp"
|
||||
#include "../helpers/Drm.hpp"
|
||||
#include "../event/EventBus.hpp"
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <gbm.h>
|
||||
#include <cairo/cairo.h>
|
||||
#include <hyprutils/math/Region.hpp>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Hyprutils::Utils;
|
||||
|
|
@ -406,7 +410,7 @@ bool CPointerManager::setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquam
|
|||
return true;
|
||||
}
|
||||
|
||||
SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
|
||||
SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager::SMonitorPointerState> state, SP<ITexture> texture) {
|
||||
auto maxSize = state->monitor->m_output->cursorPlaneSize();
|
||||
auto const& cursorSize = m_currentCursorImage.size;
|
||||
|
||||
|
|
@ -539,24 +543,23 @@ 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)
|
||||
const auto SCALE = texture->m_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale);
|
||||
cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.y);
|
||||
const auto SX = SCALE.x, SY = SCALE.y;
|
||||
const auto BW = sc<double>(DMABUF.size.x), BH = sc<double>(DMABUF.size.y);
|
||||
|
||||
if (TR) {
|
||||
cairo_matrix_rotate(&matrixPre, M_PI_2 * sc<double>(TR));
|
||||
|
||||
// FIXME: this is wrong, and doesn't work for 5, 6 and 7. (flipped + rot)
|
||||
// cba to do it rn, does anyone fucking use that??
|
||||
if (TR >= WL_OUTPUT_TRANSFORM_FLIPPED) {
|
||||
cairo_matrix_scale(&matrixPre, -1, 1);
|
||||
cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0);
|
||||
}
|
||||
|
||||
if (TR == 3 || TR == 7)
|
||||
cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0);
|
||||
else if (TR == 2 || TR == 6)
|
||||
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 matrix maps destination coords to source coords (inverse of visual transform).
|
||||
// x_src = xx * x_dst + xy * y_dst + x0
|
||||
// y_src = yx * x_dst + yy * y_dst + y0
|
||||
// cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0)
|
||||
switch (TR) {
|
||||
case WL_OUTPUT_TRANSFORM_NORMAL:
|
||||
default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break;
|
||||
case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break;
|
||||
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;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break;
|
||||
case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break;
|
||||
}
|
||||
|
||||
cairo_pattern_set_matrix(PATTERNPRE, &matrixPre);
|
||||
|
|
@ -589,15 +592,14 @@ SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager
|
|||
|
||||
RBO->bind();
|
||||
|
||||
const auto& damageSize = state->monitor->m_output->cursorPlaneSize();
|
||||
g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, damageSize.x, damageSize.y}, RBO);
|
||||
g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, INT_MAX, INT_MAX}, RBO);
|
||||
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()};
|
||||
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());
|
||||
|
||||
g_pHyprOpenGL->renderTexture(texture, xbox, {});
|
||||
g_pHyprOpenGL->renderTexture(texture, xbox, {.noCM = true});
|
||||
|
||||
g_pHyprOpenGL->end();
|
||||
g_pHyprOpenGL->m_renderData.pMonitor.reset();
|
||||
|
|
@ -901,13 +903,13 @@ const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() {
|
|||
return m_currentCursorImage;
|
||||
}
|
||||
|
||||
SP<CTexture> CPointerManager::getCurrentCursorTexture() {
|
||||
SP<ITexture> CPointerManager::getCurrentCursorTexture() {
|
||||
if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture))
|
||||
return nullptr;
|
||||
|
||||
if (m_currentCursorImage.pBuffer) {
|
||||
if (!m_currentCursorImage.bufferTex)
|
||||
m_currentCursorImage.bufferTex = makeShared<CTexture>(m_currentCursorImage.pBuffer, true);
|
||||
m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true);
|
||||
return m_currentCursorImage.bufferTex;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
class CMonitor;
|
||||
class IHID;
|
||||
class CTexture;
|
||||
class ITexture;
|
||||
|
||||
AQUAMARINE_FORWARD(IBuffer);
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ class CPointerManager {
|
|||
|
||||
struct SCursorImage {
|
||||
SP<Aquamarine::IBuffer> pBuffer;
|
||||
SP<CTexture> bufferTex;
|
||||
SP<ITexture> bufferTex;
|
||||
WP<Desktop::View::CWLSurface> surface;
|
||||
|
||||
Vector2D hotspot;
|
||||
|
|
@ -83,7 +83,7 @@ class CPointerManager {
|
|||
};
|
||||
|
||||
const SCursorImage& currentCursorImage();
|
||||
SP<CTexture> getCurrentCursorTexture();
|
||||
SP<ITexture> getCurrentCursorTexture();
|
||||
|
||||
struct {
|
||||
CSignalT<> cursorChanged;
|
||||
|
|
@ -181,7 +181,7 @@ class CPointerManager {
|
|||
std::vector<SP<SMonitorPointerState>> m_monitorStates;
|
||||
SP<SMonitorPointerState> stateFor(PHLMONITOR mon);
|
||||
bool attemptHardwareCursor(SP<SMonitorPointerState> state);
|
||||
SP<Aquamarine::IBuffer> renderHWCursorBuffer(SP<SMonitorPointerState> state, SP<CTexture> texture);
|
||||
SP<Aquamarine::IBuffer> renderHWCursorBuffer(SP<SMonitorPointerState> state, SP<ITexture> texture);
|
||||
bool setHWCursorBuffer(SP<SMonitorPointerState> state, SP<Aquamarine::IBuffer> buf);
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -847,6 +847,9 @@ void CInputManager::processMouseDownKill(const IPointer::SButtonEvent& e) {
|
|||
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(PWINDOW->getPID(), SIGKILL);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -169,10 +169,10 @@ bool CCursorshareSession::copy() {
|
|||
return false;
|
||||
}
|
||||
|
||||
CFramebuffer outFB;
|
||||
outFB.alloc(m_bufferSize.x, m_bufferSize.y, m_format);
|
||||
auto outFB = g_pHyprRenderer->createFB();
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -182,8 +182,8 @@ bool CCursorshareSession::copy() {
|
|||
g_pHyprRenderer->endRender();
|
||||
|
||||
g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor;
|
||||
outFB.bind();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID());
|
||||
outFB->bind();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID());
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ bool CCursorshareSession::copy() {
|
|||
g_pHyprOpenGL->m_renderData.pMonitor.reset();
|
||||
|
||||
m_pendingFrame.buffer->endDataPtr();
|
||||
outFB.unbind();
|
||||
GLFB(outFB)->unbind();
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "../../helpers/Monitor.hpp"
|
||||
#include "../../desktop/view/Window.hpp"
|
||||
#include "../../desktop/state/FocusState.hpp"
|
||||
#include <hyprutils/math/Region.hpp>
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
|
|
@ -133,7 +134,7 @@ void CScreenshareFrame::copy() {
|
|||
// 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);
|
||||
if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {
|
||||
if (!m_session->m_tempFB.isAllocated())
|
||||
if (!m_session->m_tempFB || !m_session->m_tempFB->isAllocated())
|
||||
storeTempFB();
|
||||
|
||||
// don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty
|
||||
|
|
@ -159,7 +160,7 @@ void CScreenshareFrame::renderMonitor() {
|
|||
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
auto TEXTURE = makeShared<CTexture>(PMONITOR->m_output->state->state().buffer);
|
||||
auto TEXTURE = g_pHyprRenderer->createTexture(PMONITOR->m_output->state->state().buffer);
|
||||
|
||||
const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client);
|
||||
g_pHyprOpenGL->m_renderData.transformDamage = false;
|
||||
|
|
@ -295,8 +296,11 @@ void CScreenshareFrame::renderWindow() {
|
|||
return;
|
||||
|
||||
auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource);
|
||||
if (!pointerSurface)
|
||||
return;
|
||||
|
||||
if (!pointerSurface || pointerSurface->getSurfaceBoxGlobal()->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())
|
||||
auto box = pointerSurface->getSurfaceBoxGlobal();
|
||||
if (!box.has_value() || box->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())
|
||||
return;
|
||||
|
||||
if (Desktop::focusState()->window() != m_session->m_window)
|
||||
|
|
@ -322,10 +326,10 @@ void CScreenshareFrame::render() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_session->m_tempFB.isAllocated()) {
|
||||
if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) {
|
||||
CBox texbox = {{}, m_bufferSize};
|
||||
g_pHyprOpenGL->renderTexture(m_session->m_tempFB.getTexture(), texbox, {});
|
||||
m_session->m_tempFB.release();
|
||||
g_pHyprOpenGL->renderTexture(m_session->m_tempFB->getTexture(), texbox, {});
|
||||
m_session->m_tempFB->release();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -378,12 +382,12 @@ bool CScreenshareFrame::copyShm() {
|
|||
return false;
|
||||
}
|
||||
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
CFramebuffer outFB;
|
||||
outFB.alloc(m_bufferSize.x, m_bufferSize.y, shm.format);
|
||||
auto outFB = g_pHyprRenderer->createFB();
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -395,8 +399,8 @@ bool CScreenshareFrame::copyShm() {
|
|||
g_pHyprRenderer->endRender();
|
||||
|
||||
g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR;
|
||||
outFB.bind();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID());
|
||||
outFB->bind();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID());
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
|
|
@ -438,7 +442,7 @@ bool CScreenshareFrame::copyShm() {
|
|||
});
|
||||
}
|
||||
|
||||
outFB.unbind();
|
||||
GLFB(outFB)->unbind();
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
|
|
@ -453,13 +457,13 @@ bool CScreenshareFrame::copyShm() {
|
|||
}
|
||||
|
||||
void CScreenshareFrame::storeTempFB() {
|
||||
g_pHyprRenderer->makeEGLCurrent();
|
||||
|
||||
m_session->m_tempFB.alloc(m_bufferSize.x, m_bufferSize.y);
|
||||
if (!m_session->m_tempFB)
|
||||
m_session->m_tempFB = g_pHyprRenderer->createFB();
|
||||
m_session->m_tempFB->alloc(m_bufferSize.x, m_bufferSize.y);
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,15 +145,19 @@ WP<CScreenshareSession> CScreenshareManager::getManagedSession(eScreenshareType
|
|||
auto& session = *it;
|
||||
|
||||
session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() {
|
||||
std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return !s || session.expired() || s->m_session == session->m_session; });
|
||||
if (!session.expired())
|
||||
std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); });
|
||||
});
|
||||
|
||||
return session->m_session;
|
||||
}
|
||||
|
||||
void CScreenshareManager::destroyClientSessions(wl_client* client) {
|
||||
LOGM(Log::TRACE, "Destroy client sessions for {:x}", (uintptr_t)client);
|
||||
std::erase_if(m_managedSessions, [&](const auto& session) { return !session || session->m_session->m_client == client; });
|
||||
bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) {
|
||||
return std::ranges::any_of(m_sessions, [monitor](const auto& s) {
|
||||
if (!s)
|
||||
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)) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace Screenshare {
|
|||
std::vector<DRMFormat> m_formats;
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
|
||||
CFramebuffer m_tempFB;
|
||||
SP<IFramebuffer> m_tempFB;
|
||||
|
||||
SP<CEventLoopTimer> m_shareStopTimer;
|
||||
bool m_sharing = false;
|
||||
|
|
@ -206,9 +206,8 @@ namespace Screenshare {
|
|||
|
||||
UP<CCursorshareSession> newCursorSession(wl_client* client, WP<CWLPointerResource> pointer);
|
||||
|
||||
void destroyClientSessions(wl_client* client);
|
||||
|
||||
void onOutputCommit(PHLMONITOR monitor);
|
||||
bool isOutputBeingSSd(PHLMONITOR monitor);
|
||||
|
||||
private:
|
||||
std::vector<WP<CScreenshareSession>> m_sessions;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion,
|
|||
|
||||
CScreenshareSession::~CScreenshareSession() {
|
||||
stop();
|
||||
LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}", m_type, m_name);
|
||||
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 ({}): {}, {:x}", m_type, m_name, ptr);
|
||||
}
|
||||
|
||||
void CScreenshareSession::stop() {
|
||||
|
|
@ -52,6 +53,9 @@ void CScreenshareSession::stop() {
|
|||
}
|
||||
|
||||
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>(
|
||||
std::chrono::milliseconds(500),
|
||||
[this](SP<CEventLoopTimer> self, void* data) {
|
||||
|
|
@ -99,7 +103,7 @@ void CScreenshareSession::calculateConstraints() {
|
|||
m_name = PMONITOR->m_name;
|
||||
break;
|
||||
case SHARE_WINDOW:
|
||||
m_bufferSize = m_window->m_realSize->value().round();
|
||||
m_bufferSize = (m_window->m_realSize->value() * PMONITOR->m_scale).round();
|
||||
m_name = m_window->m_title;
|
||||
break;
|
||||
case SHARE_REGION:
|
||||
|
|
@ -121,7 +125,7 @@ void CScreenshareSession::screenshareEvents(bool startSharing) {
|
|||
m_sharing = true;
|
||||
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)});
|
||||
LOGM(Log::INFO, "New screenshare session for ({}): {}", m_type, m_name);
|
||||
LOGM(Log::INFO, "Started screenshare session for ({}): {}", m_type, m_name);
|
||||
|
||||
Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name);
|
||||
} else if (!startSharing && m_sharing) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include "color-management-v1.hpp"
|
||||
#include "../helpers/Monitor.hpp"
|
||||
#include "core/Output.hpp"
|
||||
#include "types/ColorManagement.hpp"
|
||||
#include "../helpers/cm/ColorManagement.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
using namespace NColorManagement;
|
||||
|
|
@ -388,12 +388,6 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP<CWpColorMana
|
|||
RESOURCE->m_settings = m_surface->getPreferredImageDescription();
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
@ -429,7 +423,7 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
|
|||
LOGM(Log::TRACE, "Create image description from icc for id {}", id);
|
||||
|
||||
// FIXME actually check completeness
|
||||
if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
|
||||
if (m_icc.fd < 0 || !m_icc.length) {
|
||||
r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings");
|
||||
return;
|
||||
}
|
||||
|
|
@ -443,10 +437,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
|
|||
return;
|
||||
}
|
||||
|
||||
LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_settings.icc.fd, m_settings.icc.offset, m_settings.icc.length, id);
|
||||
LOGM(Log::ERR, "FIXME: Parse icc file {}({},{}) for id {}", m_icc.fd, m_icc.offset, m_icc.length, id);
|
||||
|
||||
// FIXME actually check support
|
||||
if (m_settings.icc.fd < 0 || !m_settings.icc.length) {
|
||||
if (m_icc.fd < 0 || !m_icc.length) {
|
||||
RESOURCE->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported");
|
||||
return;
|
||||
}
|
||||
|
|
@ -459,9 +453,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SP<CWpImageDescriptionCre
|
|||
});
|
||||
|
||||
m_resource->setSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) {
|
||||
m_settings.icc.fd = fd;
|
||||
m_settings.icc.offset = offset;
|
||||
m_settings.icc.length = length;
|
||||
m_icc.fd = fd;
|
||||
m_icc.offset = offset;
|
||||
m_icc.length = length;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -731,8 +725,9 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP<CW
|
|||
|
||||
const auto toProto = [](float value) { return sc<int32_t>(std::round(value * PRIMARIES_SCALE)); };
|
||||
|
||||
if (m_settings.icc.fd >= 0)
|
||||
m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length);
|
||||
// FIXME:
|
||||
// if (m_icc.fd >= 0)
|
||||
// m_resource->sendIccFile(m_icc.fd, m_icc.length);
|
||||
|
||||
// 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),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include "../helpers/Monitor.hpp"
|
||||
#include "core/Compositor.hpp"
|
||||
#include "color-management-v1.hpp"
|
||||
#include "types/ColorManagement.hpp"
|
||||
#include "../helpers/cm/ColorManagement.hpp"
|
||||
|
||||
class CColorManager;
|
||||
class CColorManagementOutput;
|
||||
|
|
@ -109,6 +109,14 @@ class CColorManagementIccCreator {
|
|||
WP<CColorManagementIccCreator> m_self;
|
||||
|
||||
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:
|
||||
SP<CWpImageDescriptionCreatorIccV1> m_resource;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ CGammaControl::CGammaControl(SP<CZwlrGammaControlV1> resource_, wl_resource* out
|
|||
|
||||
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
|
||||
int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0);
|
||||
if UNLIKELY (fdFlags < 0) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ CScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_) : m
|
|||
return;
|
||||
|
||||
m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); });
|
||||
m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) {
|
||||
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
||||
PROTO::screencopy->destroyResource(this);
|
||||
});
|
||||
m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); });
|
||||
m_resource->setCaptureOutput(
|
||||
[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,
|
||||
|
|
@ -25,10 +22,6 @@ CScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_) : m
|
|||
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) {
|
||||
const auto PMONITORRES = CWLOutputResource::fromResource(output);
|
||||
if (!PMONITORRES || !PMONITORRES->m_monitor) {
|
||||
|
|
@ -69,11 +62,6 @@ CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScr
|
|||
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_listeners.stopped = m_session->m_events.stopped.listen([this]() {
|
||||
if (good())
|
||||
m_resource->sendFailed();
|
||||
});
|
||||
|
||||
m_frame = m_session->nextFrame(overlayCursor);
|
||||
|
||||
auto formats = m_session->allowedFormats();
|
||||
|
|
@ -87,7 +75,14 @@ CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScr
|
|||
auto bufSize = m_frame->bufferSize();
|
||||
|
||||
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);
|
||||
|
||||
if (m_resource->version() >= 3) {
|
||||
|
|
@ -104,6 +99,12 @@ void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* b
|
|||
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) {
|
||||
LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this);
|
||||
m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ namespace Screenshare {
|
|||
class CScreencopyClient {
|
||||
public:
|
||||
CScreencopyClient(SP<CZwlrScreencopyManagerV1> resource_);
|
||||
~CScreencopyClient();
|
||||
|
||||
bool good();
|
||||
|
||||
|
|
@ -52,10 +51,7 @@ class CScreencopyFrame {
|
|||
Time::steady_tp m_timestamp;
|
||||
bool m_overlayCursor = true;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener stopped;
|
||||
} m_listeners;
|
||||
|
||||
//
|
||||
void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage);
|
||||
|
||||
friend class CScreencopyProtocol;
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColo
|
|||
|
||||
m_opaque = col_.a >= 1.F;
|
||||
|
||||
m_texture = makeShared<CTexture>(DRM_FORMAT_ARGB8888, rc<uint8_t*>(&m_color), 4, Vector2D{1, 1});
|
||||
m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc<uint8_t*>(&m_color), 4, Vector2D{1, 1});
|
||||
|
||||
m_resource = CWLBufferResource::create(makeShared<CWlBuffer>(client, 1, id));
|
||||
|
||||
m_success = m_texture->m_texID;
|
||||
m_success = m_texture->ok();
|
||||
|
||||
size = {1, 1};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1
|
|||
if UNLIKELY (!good())
|
||||
return;
|
||||
|
||||
m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) {
|
||||
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
||||
PROTO::toplevelExport->destroyResource(this);
|
||||
});
|
||||
m_resource->setOnDestroy([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) {
|
||||
captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle));
|
||||
|
|
@ -28,10 +25,6 @@ CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1
|
|||
m_savedClient = m_resource->client();
|
||||
}
|
||||
|
||||
CToplevelExportClient::~CToplevelExportClient() {
|
||||
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
||||
}
|
||||
|
||||
void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) {
|
||||
auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle);
|
||||
|
||||
|
|
@ -63,11 +56,6 @@ CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> re
|
|||
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_listeners.stopped = m_session->m_events.stopped.listen([this]() {
|
||||
if (good())
|
||||
m_resource->sendFailed();
|
||||
});
|
||||
|
||||
m_frame = m_session->nextFrame(overlayCursor);
|
||||
|
||||
auto formats = m_session->allowedFormats();
|
||||
|
|
@ -100,6 +88,12 @@ void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) {
|
|||
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) {
|
||||
LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this);
|
||||
m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used");
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ namespace Screenshare {
|
|||
class CToplevelExportClient {
|
||||
public:
|
||||
CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_);
|
||||
~CToplevelExportClient();
|
||||
|
||||
bool good();
|
||||
|
||||
|
|
@ -50,10 +49,7 @@ class CToplevelExportFrame {
|
|||
CHLBufferReference m_buffer;
|
||||
Time::steady_tp m_timestamp;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener stopped;
|
||||
} m_listeners;
|
||||
|
||||
//
|
||||
void shareFrame(wl_resource* buffer, bool ignoreDamage);
|
||||
|
||||
friend class CToplevelExportProtocol;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "../../helpers/math/Math.hpp"
|
||||
#include "../../helpers/time/Time.hpp"
|
||||
#include "../types/Buffer.hpp"
|
||||
#include "../types/ColorManagement.hpp"
|
||||
#include "../../helpers/cm/ColorManagement.hpp"
|
||||
#include "../types/SurfaceRole.hpp"
|
||||
#include "../types/SurfaceState.hpp"
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue