Merge branch 'hyprwm:main' into lua/update-lockscreen-crash-message

This commit is contained in:
ImperishableSecret 2026-05-06 00:39:50 +05:30 committed by GitHub
commit ebce096476
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
100 changed files with 2524 additions and 947 deletions

View file

@ -18,7 +18,10 @@ jobs:
run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
- name: Check exit status
run: grep 0 result/exit_status
run: |
grep 0 result/exit_status || echo "hyprtester failed"
grep 0 result/exit_status_gtests || echo "gtests failed"
[ 0 = $(cat result/exit_status) ] && [ 0 = $(cat result/exit_status_gtests) ]
- name: Upload artifacts
if: always()

View file

@ -104,6 +104,8 @@ add_compile_options(
-Wall
-Wextra
-Wpedantic
-Wno-keyword-macro
-Wno-unused-result
-Wno-unused-parameter
-Wno-unused-value
-Wno-missing-field-initializers
@ -131,7 +133,7 @@ find_package(glslang CONFIG REQUIRED)
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
set(HYPRCURSOR_MINIMUM_VERSION 0.1.7)
set(HYPRUTILS_MINIMUM_VERSION 0.11.1)
set(HYPRUTILS_MINIMUM_VERSION 0.13.0)
set(HYPRGRAPHICS_MINIMUM_VERSION 0.5.1)
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION})
@ -270,7 +272,9 @@ pkg_check_modules(
re2
muparser
lcms2
lua55)
)
pkg_search_module(LUA REQUIRED IMPORTED_TARGET GLOBAL lua55 lua5.5 lua-55 lua-5.5 lua>=5.5 lua<5.6)
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
@ -288,8 +292,8 @@ add_library(hyprland_lib STATIC ${SRCFILES})
add_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES})
target_link_libraries(Hyprland hyprland_lib)
target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS})
target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS})
target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS} ${LUA_INCLUDE_DIRS})
target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS} ${LUA_INCLUDE_DIRS})
set(USE_GPROF OFF)
@ -423,6 +427,7 @@ target_link_libraries(
PkgConfig::hyprcursor_dep
PkgConfig::hyprgraphics_dep
PkgConfig::deps
PkgConfig::LUA
)
target_link_libraries(

View file

@ -139,10 +139,13 @@ hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1}
hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1} } })
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
-- Default springs
hl.curve("easy", { type = "spring", mass = 1, stiffness = 71.2633, dampening = 15.8273644 })
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, spring = "easy", style = "popin 87%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })

42
flake.lock generated
View file

@ -16,11 +16,11 @@
]
},
"locked": {
"lastModified": 1776876344,
"narHash": "sha256-Ubqb/agkuMJK+k19gjQgHux/eOYRc1sRGoOZOho8+VY=",
"lastModified": 1777499565,
"narHash": "sha256-nU55VWk99Pn1QzQDDjFISocC4SgDZ3Xp+zb6ji3JclM=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "648a13d0ee1e03a843b3e145b8ece15393058701",
"rev": "813c1e8981893c11e118b19c125d6bc282f51765",
"type": "github"
},
"original": {
@ -193,11 +193,11 @@
]
},
"locked": {
"lastModified": 1776426736,
"narHash": "sha256-rl7i4aY+9p8LysJp7o8uRWahCkpFznCgGHXszlTw7b0=",
"lastModified": 1777320127,
"narHash": "sha256-Qu+Wf2Bp5qUjyn2YpZNq8a7JyzTGowhT1knrwE38a9U=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "7833ff33b2e82d3406337b5dcf0d1cec595d83e9",
"rev": "090117506ddc3d7f26e650ff344d378c2ec329cc",
"type": "github"
},
"original": {
@ -261,11 +261,11 @@
]
},
"locked": {
"lastModified": 1777148223,
"narHash": "sha256-PTf7kRFFzCW6rIYxLH2fWfVJmj86FSYe3k6L8B+IM9o=",
"lastModified": 1777492286,
"narHash": "sha256-PwuoEJQcjSKJNP5T55qhfDwIP0tw5zxEhfu8GDfKfeg=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "fa3992be2dfebe4ab06d753c6ca59bea298e798f",
"rev": "ec5c0c709706bad5b82f667fd8758eae442577ce",
"type": "github"
},
"original": {
@ -284,11 +284,11 @@
]
},
"locked": {
"lastModified": 1777148232,
"narHash": "sha256-Uv0WZLhu89SafuSOmYDA7akrPt4wBRmsa1ucasO5aXg=",
"lastModified": 1777159683,
"narHash": "sha256-Jxixw6wZphUp+nHYxOKUYSckL17QMBx2d5Zp0rJHr1g=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "fec9cf1abcc1011e46f0a0986f46bf93c6bf8b92",
"rev": "b8632713a6beaf28b56f2a7b0ab2fb7088dbb404",
"type": "github"
},
"original": {
@ -310,11 +310,11 @@
]
},
"locked": {
"lastModified": 1776728575,
"narHash": "sha256-z9eGphrArEBpl1O/GCH0wlY6z4K9vA6yWh2gAS6qytU=",
"lastModified": 1777388329,
"narHash": "sha256-40YxVGF2rA9iH3D7am5fy4EOSBbMgpJtJ9yhl0Cx+qI=",
"owner": "hyprwm",
"repo": "hyprwire",
"rev": "f3a80888783702a39691b684d099e16b83ed4702",
"rev": "04be2897e05f9b271d532b5ae56ca088d2eeac02",
"type": "github"
},
"original": {
@ -325,11 +325,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1776877367,
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"lastModified": 1777954456,
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
"type": "github"
},
"original": {
@ -415,11 +415,11 @@
]
},
"locked": {
"lastModified": 1777035886,
"narHash": "sha256-m1TNuBoSXUBSKhD9UVMkU90M0wFTPTfvIOOltO8IM8A=",
"lastModified": 1777585783,
"narHash": "sha256-JTeWRy42VElroJ0rVdZuVXSoTLsx+NzQfGPKMbtn3SU=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "ecfcdcc781f48821d83e1e2a0e30d7beca0eeb5e",
"rev": "fa50d6fbaff8f42c61071b87b034a90d82a33558",
"type": "github"
},
"original": {

View file

@ -1,6 +1,7 @@
#include <sys/poll.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <print>
#include <wayland-client.h>

View file

@ -2,11 +2,13 @@
#include <src/includes.hpp>
#include <sstream>
#include <any>
#include <cmath>
#define private public
#include <src/managers/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
#include <src/helpers/Monitor.hpp>
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>
#include <src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp>
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
@ -17,6 +19,7 @@
#undef private
#include <hyprutils/utils/ScopeGuard.hpp>
#include <hyprutils/string/Numeric.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::Utils;
using namespace Hyprutils::String;
@ -156,6 +159,96 @@ static SDispatchResult simulateGesture(std::string in) {
return {.success = true};
}
static SDispatchResult pinchUpdate(std::string in) {
CVarList data(in);
uint32_t fingers = 2;
double scale = 1.0;
Vector2D delta = {};
double rotation{};
if (data.size() < 2)
return {.success = false, .error = "invalid input"};
if (const auto n = strToNumber<uint32_t>(data[0]); n)
fingers = n.value();
else
return {.success = false, .error = "invalid input"};
if (const auto n = strToNumber<double>(data[1]); n)
scale = n.value();
else
return {.success = false, .error = "invalid input"};
if (data.size() > 2) {
if (const auto n = strToNumber<double>(data[2]); n)
delta.x = n.value();
else
return {.success = false, .error = "invalid input"};
}
if (data.size() > 3) {
if (const auto n = strToNumber<double>(data[3]); n)
delta.y = n.value();
else
return {.success = false, .error = "invalid input"};
}
if (data.size() > 4) {
if (const auto n = strToNumber<double>(data[4]); n)
rotation = n.value();
else
return {.success = false, .error = "invalid input"};
}
g_pTrackpadGestures->gestureUpdate(IPointer::SPinchUpdateEvent{
.fingers = fingers,
.delta = delta,
.scale = scale,
.rotation = rotation,
});
return {};
}
static SDispatchResult pinchEnd(std::string in) {
g_pTrackpadGestures->gestureEnd(IPointer::SPinchEndEvent{});
return {};
}
static SDispatchResult expectCursorZoom(std::string in) {
CVarList data(in);
float expected = 1.F;
float delta = 0.01F;
if (data.size() < 1)
return {.success = false, .error = "invalid input"};
if (const auto n = strToNumber<float>(data[0]); n)
expected = n.value();
else
return {.success = false, .error = "invalid input"};
if (data.size() > 1) {
if (const auto n = strToNumber<float>(data[1]); n)
delta = n.value();
else
return {.success = false, .error = "invalid input"};
}
const auto PMONITOR = g_pCompositor->getMonitorFromVector(g_pInputManager->getMouseCoordsInternal());
if (!PMONITOR)
return {.success = false, .error = "No monitor under cursor"};
const auto actual = PMONITOR->m_cursorZoom->value();
if (std::abs(actual - expected) > delta)
return {.success = false, .error = std::format("Expected cursor zoom {} ± {}, got {}", expected, delta, actual)};
return {};
}
static SDispatchResult vkb(std::string in) {
auto tkb0 = CTestKeyboard::create(false);
auto tkb1 = CTestKeyboard::create(false);
@ -336,7 +429,7 @@ static SDispatchResult floatingFocusOnFullscreen(std::string in) {
if (!PLASTWINDOW->m_isFloating)
return {.success = false, .error = "Window must be floating"};
if (PLASTWINDOW->m_alpha != 1.f)
if (PLASTWINDOW->alphaTotal() != 1.F)
return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"};
if (!PLASTWINDOW->m_createdOverFullscreen)
@ -375,6 +468,32 @@ static int luaGesture(lua_State* L) {
return luaResult(L, ::simulateGesture(std::format("{},{}", direction, fingers)));
}
static int luaPinchUpdate(lua_State* L) {
std::string in = std::format("{},{}", (int)luaL_checkinteger(L, 1), (double)luaL_checknumber(L, 2));
if (lua_gettop(L) > 2)
in += std::format(",{}", (double)luaL_checknumber(L, 3));
if (lua_gettop(L) > 3)
in += std::format(",{}", (double)luaL_checknumber(L, 4));
if (lua_gettop(L) > 4)
in += std::format(",{}", (double)luaL_checknumber(L, 5));
return luaResult(L, ::pinchUpdate(in));
}
static int luaPinchEnd(lua_State* L) {
return luaResult(L, ::pinchEnd(""));
}
static int luaExpectCursorZoom(lua_State* L) {
const auto expected = (double)luaL_checknumber(L, 1);
if (lua_gettop(L) > 1)
return luaResult(L, ::expectCursorZoom(std::format("{},{}", expected, (double)luaL_checknumber(L, 2))));
return luaResult(L, ::expectCursorZoom(std::format("{}", expected)));
}
static int luaScroll(lua_State* L) {
return luaResult(L, ::scroll(std::to_string((double)luaL_checknumber(L, 1))));
}
@ -425,6 +544,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addLuaFn("vkb", ::luaVkb);
addLuaFn("alt", ::luaAlt);
addLuaFn("gesture", ::luaGesture);
addLuaFn("pinch_update", ::luaPinchUpdate);
addLuaFn("pinch_end", ::luaPinchEnd);
addLuaFn("expect_cursor_zoom", ::luaExpectCursorZoom);
addLuaFn("scroll", ::luaScroll);
addLuaFn("click", ::luaClick);
addLuaFn("keybind", ::luaKeybind);

View file

@ -126,6 +126,9 @@ static bool sendScroll(int delta) {
}
TEST_CASE(pointerScroll) {
NLog::log("{}Skipping pointerScroll test (unstable in CI / headless environments)", Colors::YELLOW);
return;
std::optional<CClient> client;
try {
client.emplace();

View file

@ -151,8 +151,10 @@ static bool isCursorPos(int x, int y) {
}
TEST_CASE(pointerWarp) {
std::optional<CClient> client;
NLog::log("{}Skipping pointerWarp test (unstable in CI / headless environments)", Colors::YELLOW);
return;
std::optional<CClient> client;
try {
client.emplace();
} catch (...) { FAIL_TEST("Couldn't start the client"); }

View file

@ -8,7 +8,7 @@
TEST_CASE(monitorsColorManagement) {
std::string monitorsSpec = getFromSocket("j/monitors");
ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset")");
ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset": )");
ASSERT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'wide' })"), "ok");
@ -16,14 +16,14 @@ TEST_CASE(monitorsColorManagement) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
monitorsSpec = getFromSocket("j/monitors");
ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")");
ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset": )");
ASSERT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'srgb', sdrbrightness = 1.2, sdrsaturation = 0.98 })"), "ok");
monitorsSpec = getFromSocket("j/monitors");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
ASSERT_CONTAINS(monitorsSpec, "colorManagementPreset");
ASSERT_CONTAINS(monitorsSpec, "sdrBrightness");
ASSERT_CONTAINS(monitorsSpec, "sdrSaturation");
ASSERT_CONTAINS(monitorsSpec, R"("colorManagementPreset": )");
ASSERT_CONTAINS(monitorsSpec, R"("sdrBrightness": )");
ASSERT_CONTAINS(monitorsSpec, R"("sdrSaturation": )");
}

View file

@ -204,7 +204,7 @@ TEST_CASE(dwindleForceSplitOnMoveToWorkspace) {
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
ASSERT(!!Tests::spawnKitty("kitty"), true);
std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
std::string posBefore = "at: " + Tests::getAttribute(getFromSocket("/activewindow"), "at");
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move_to_corner({ corner = 3 })")); // top left

View file

@ -9,9 +9,9 @@
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {
std::string activeWin = getFromSocket("/activewindow");
auto winClass = Tests::getWindowAttribute(activeWin, "class:");
auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back();
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
auto winClass = Tests::getAttribute(activeWin, "class");
auto winFullscreen = Tests::getAttribute(activeWin, "fullscreen").back();
if (winClass == class_ && winFullscreen == fullscreen)
return true;
else {
if (log)

View file

@ -6,12 +6,14 @@
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/string/Numeric.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
using namespace Hyprutils::String;
#define UP CUniquePointer
#define SP CSharedPointer
@ -178,4 +180,40 @@ TEST_CASE(gestures) {
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
}
const std::string cursorPosBeforePinch = getFromSocket("/cursorpos");
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 500, y = 500 })"));
OK(getFromSocket("/eval hl.config({ cursor = { zoom_factor = 1 } })"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)"));
OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 1.2)"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.2, 0.01)"));
OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 1.6)"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.6, 0.01)"));
OK(getFromSocket("/eval hl.plugin.test.pinch_end()"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1.6, 0.01)"));
OK(getFromSocket("/eval hl.plugin.test.pinch_update(2, 0.64)"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)"));
OK(getFromSocket("/eval hl.plugin.test.pinch_end()"));
OK(getFromSocket("/eval hl.plugin.test.expect_cursor_zoom(1, 0.01)"));
const auto comma = cursorPosBeforePinch.find(',');
if (comma != std::string::npos) {
auto xSv = std::string_view(cursorPosBeforePinch).substr(0, comma);
auto ySv = std::string_view(cursorPosBeforePinch).substr(comma + 1);
while (!xSv.empty() && xSv.front() == ' ')
xSv.remove_prefix(1);
while (!ySv.empty() && ySv.front() == ' ')
ySv.remove_prefix(1);
const auto x = strToNumber<int>(xSv);
const auto y = strToNumber<int>(ySv);
if (!x || !y)
FAIL_TEST("Failed to restore cursor pos");
OK(getFromSocket(std::format("/dispatch hl.dsp.cursor.move({{ x = {}, y = {} }})", x.value(), y.value())));
}
}

View file

@ -192,6 +192,7 @@ TEST_CASE(groups) {
NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })"));
NLog::log("{}Spawn kittyProcC", Colors::YELLOW);
auto kittyProcC = Tests::spawnKitty();
@ -206,6 +207,7 @@ TEST_CASE(groups) {
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 0 } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"));
OK(getFromSocket("/dispatch hl.dsp.group.active({ index = 1 })"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
@ -398,4 +400,43 @@ TEST_CASE(groups) {
Tests::killAllWindows();
ASSERT(Tests::windowCount(), 0);
// Test groupbar middle click close config
{
OK(getFromSocket("/eval hl.config({ group = { auto_group = true, groupbar = { enabled = true, middle_click_close = false } } })"));
auto kittyA = Tests::spawnKitty("kittyA");
if (!kittyA) {
FAIL_TEST("Could not spawn kitty");
}
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto kittyB = Tests::spawnKitty("kittyB");
if (!kittyB) {
FAIL_TEST("Could not spawn kitty");
}
EXPECT(Tests::windowCount(), 2);
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 80, y = 32 })"));
OK(getFromSocket("/eval hl.plugin.test.click(274, 1)"));
OK(getFromSocket("/eval hl.plugin.test.click(274, 0)"));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
EXPECT(Tests::windowCount(), 2);
OK(getFromSocket("/eval hl.config({ group = { groupbar = { middle_click_close = true } } })"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 80, y = 32 })"));
OK(getFromSocket("/eval hl.plugin.test.click(274, 1)"));
OK(getFromSocket("/eval hl.plugin.test.click(274, 0)"));
Tests::waitUntilWindowsN(1);
EXPECT(Tests::windowCount(), 1);
OK(getFromSocket("/eval hl.config({ group = { groupbar = { enabled = 0 } } })"));
}
Tests::killAllWindows();
ASSERT(Tests::windowCount(), 0);
}

View file

@ -547,6 +547,14 @@ SUBTEST(perDeviceKeybind) {
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER + Y')"), "ok");
// Tags
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { device = { inclusive = true, list = { 'test-tag' } } })"), "ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER + Y')"), "ok");
}
SUBTEST(unbind) {

View file

@ -1,6 +1,7 @@
#include "../shared.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <algorithm>
#include "tests.hpp"
TEST_CASE(focusMasterPrevious) {
@ -141,3 +142,71 @@ TEST_CASE(fsBehavior) {
EXPECT_CONTAINS(str, "fullscreen: 0");
}
}
TEST_CASE(rollFocus) {
// test rollnext/rollprev dispatchers
OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })"));
// set up windows
std::vector<std::string> windows = {"slave1", "slave2", "slave3", "master"};
// helper lambda thing
auto roll = [&](const std::string& dir) {
auto pivot = (dir == "rollnext") ? windows.begin() + 1 : windows.end() - 1;
// rotate the windows vector along with the actual windows
// the rolling behavior of the window focus should follow the
// rotating behavior of std::ranges::rotate
OK(getFromSocket("/dispatch hl.dsp.layout('" + dir + "')"));
std::ranges::rotate(windows.begin(), pivot, windows.end());
ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: " + windows.back());
};
for (auto const& win : windows) {
if (!Tests::spawnKitty(win)) {
FAIL_TEST("Could not spawn kitty with win class `{}`", win);
}
}
// focus master
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster master')"));
ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: master");
// put the windows in the washing machine
NLog::log("{}Testing rollnext", Colors::YELLOW);
for (int i = 0; i < 20; ++i) {
roll("rollnext");
}
NLog::log("{}Testing rollprev", Colors::YELLOW);
for (int i = 0; i < 20; ++i) {
roll("rollprev");
}
NLog::log("{}Testing rollnext with rollprev", Colors::YELLOW);
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 5; ++j) {
roll("rollnext");
}
roll("rollprev");
}
NLog::log("{}Testing rollnext/rollprev alternation", Colors::YELLOW);
for (int i = 0; i < 20; ++i) {
if (i % 2 == 0) {
roll("rollnext");
} else {
roll("rollprev");
}
}
NLog::log("{}Testing rollnext/rollprev burst calls", Colors::YELLOW);
for (int i = 0; i < 20; ++i) {
if (i / 5 % 2 == 0) {
roll("rollnext");
} else {
roll("rollprev");
}
}
}

View file

@ -1,8 +1,11 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <thread>
#include <algorithm>
#include <chrono>
#include <ranges>
#include <set>
#include <thread>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "../shared.hpp"
@ -13,6 +16,13 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
SUBTEST(expectBlockedByAll, const std::string& blockedByLine, const std::set<std::string>& expectedBlockedBy) {
const std::set<std::string> blockedBy = blockedByLine | std::ranges::views::split(',') | std::ranges::to<std::set<std::string>>();
NLog::log("blockedBy = {}", blockedBy);
NLog::log("expectedBlockedBy = {}", expectedBlockedBy);
ASSERT(std::ranges::includes(blockedBy, expectedBlockedBy), true);
}
TEST_CASE(solitaryClients) {
OK(getFromSocket("/eval hl.config({ general = { allow_tearing = false } })"));
OK(getFromSocket("/eval hl.config({ render = { direct_scanout = 0 } })"));
@ -21,11 +31,12 @@ TEST_CASE(solitaryClients) {
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "solitary: 0\n");
EXPECT_CONTAINS(str, "solitaryBlockedBy: windowed mode,missing candidate");
CALL_SUBTEST(expectBlockedByAll, Tests::getAttribute(str, "solitaryBlockedBy"), {"windowed mode", "missing candidate"});
EXPECT_CONTAINS(str, "activelyTearing: false");
EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate");
CALL_SUBTEST(expectBlockedByAll, Tests::getAttribute(str, "tearingBlockedBy"),
{"next frame is not torn", "user settings", "not supported by monitor", "missing candidate"});
EXPECT_CONTAINS(str, "directScanoutTo: 0\n");
EXPECT_CONTAINS(str, "directScanoutBlockedBy: user settings,software renders/cursors,missing candidate");
CALL_SUBTEST(expectBlockedByAll, Tests::getAttribute(str, "directScanoutBlockedBy"), {"user settings", "software renders/cursors", "missing candidate"});
}
// FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time

View file

@ -75,7 +75,7 @@ TEST_CASE(swapWindow) {
// Test swapwindow by direction
{
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
auto pos = "at: " + Tests::getAttribute(getFromSocket("/activewindow"), "at");
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch hl.dsp.window.swap({ direction = 'right' })"));
@ -87,7 +87,7 @@ TEST_CASE(swapWindow) {
// Test swapwindow by class
{
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
auto pos = "at: " + Tests::getAttribute(getFromSocket("/activewindow"), "at");
NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch hl.dsp.window.swap({ target = 'class:kitty_B' })"));
@ -101,7 +101,7 @@ TEST_CASE(swapWindow) {
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })");
auto addr = getWindowAddress(getFromSocket("/activewindow"));
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
auto pos = "at: " + Tests::getAttribute(getFromSocket("/activewindow"), "at");
NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr);
OK(getFromSocket(std::format("/dispatch hl.dsp.window.swap({{ target = 'address:0x{}' }})", addr)));
@ -124,7 +124,7 @@ TEST_CASE(swapWindow) {
{
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })");
auto addr = getWindowAddress(getFromSocket("/activewindow"));
auto ws = Tests::getWindowAttribute(getFromSocket("/activewindow"), "workspace:");
auto ws = "workspace: " + Tests::getAttribute(getFromSocket("/activewindow"), "workspace");
NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr);
OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = 'name:swapwindow2', follow = false })"));
@ -190,9 +190,9 @@ TEST_CASE(windowGroupRules) {
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {
std::string activeWin = getFromSocket("/activewindow");
auto winClass = Tests::getWindowAttribute(activeWin, "class:");
auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back();
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
auto winClass = Tests::getAttribute(activeWin, "class");
auto winFullscreen = Tests::getAttribute(activeWin, "fullscreen").back();
if (winClass == class_ && winFullscreen == fullscreen)
return true;
else {
if (log)
@ -588,6 +588,28 @@ TEST_CASE(issue14038) {
// this should not crash hyprland. If we are alive, we good.
}
TEST_CASE(specialFloatRecenters) {
if (!spawnKitty("kitty_special_float_recenter"))
FAIL_TEST("Could not spawn kitty");
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty_special_float_recenter' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 10, y = 10, window = 'class:kitty_special_float_recenter' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = 'special:recenter', follow = false, window = 'class:kitty_special_float_recenter' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 50000, y = 50000, window = 'class:kitty_special_float_recenter' })"));
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('recenter')"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_special_float_recenter' })"));
const auto active = getFromSocket("/activewindow");
EXPECT_CONTAINS(active, "class: kitty_special_float_recenter");
EXPECT_CONTAINS(active, "size: 10,10");
EXPECT_CONTAINS(active, "at: 955,535");
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('recenter')"));
Tests::killAllWindows();
ASSERT(Tests::windowCount(), 0);
}
// TODO: decompose this into multiple test cases
TEST_CASE(windows) {
// test on workspace "window"

View file

@ -1,4 +1,5 @@
#include "shared.hpp"
#include <cassert>
#include <csignal>
#include <cerrno>
#include <thread>
@ -186,12 +187,14 @@ bool Tests::writeFile(const std::string& name, const std::string& contents) {
return true;
}
std::string Tests::getWindowAttribute(const std::string& winInfo, const std::string& attr) {
auto pos = winInfo.find(attr);
std::string Tests::getAttribute(const std::string& hyprlandResponse, std::string attr) {
attr += ": ";
auto pos = hyprlandResponse.find(attr);
if (pos == std::string::npos) {
NLog::log("{}Window attribute not found: '{}'", Colors::RED, attr);
return "Wrong window attribute";
}
auto pos2 = winInfo.find('\n', pos);
return winInfo.substr(pos, pos2 - pos);
pos += attr.size();
auto pos2 = hyprlandResponse.find('\n', pos);
return hyprlandResponse.substr(pos, pos2 - pos);
}

View file

@ -19,5 +19,11 @@ namespace Tests {
bool killAllLayers();
std::string execAndGet(const std::string& cmd);
bool writeFile(const std::string& name, const std::string& contents);
std::string getWindowAttribute(const std::string& winInfo, const std::string& attr);
/**
* Extracts the given attribute from Hyprland's response to requests such as `/clients`, `/workspaces`, etc.
* Automatically appends `: ` to `attr`.
*
* For example, `Tests::getAttribute(getFromSocket("/activewindow"), "at")` returns the active window's coordinates, e.g., `"2,32"`
*/
std::string getAttribute(const std::string& hyprlandResponse, std::string attr);
};

View file

@ -93,6 +93,7 @@ hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "al
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.device({ name = "test-mouse-1", enabled = true })
hl.device({ name = "test-keyboard-1", enabled = true, tags = "test-tag"})
hl.config({
dwindle = {
@ -290,5 +291,6 @@ hl.gesture({ fingers = 5, direction = "left", action = function() hl.dispatch(hl
hl.gesture({ fingers = 5, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "t", window = "activewindow" })) end })
hl.gesture({ fingers = 4, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "return", window = "activewindow" })) end })
hl.gesture({ fingers = 4, direction = "left", action = function() hl.dispatch(hl.dsp.cursor.move_to_corner({ corner = 1, window = "activewindow" })) end })
hl.gesture({ fingers = 2, direction = "pinch", action = "cursorZoom", zoom_level = "1", mode = "live" })
hl.gesture({ fingers = 2, direction = "right", action = "float", disable_inhibit = true })

View file

@ -469,7 +469,8 @@ def generate_stub(root: Path) -> str:
api_signatures: dict[str, str] = {
"hl.on": "fun(event: HL.EventName, cb: fun(...)): HL.EventSubscription",
"hl.bind": "fun(keys: string, dispatcher: function, opts?: HL.BindOptions): HL.Keybind",
"hl.bind": "fun(keys: string, dispatcher: HL.Dispatcher|function, opts?: HL.BindOptions): HL.Keybind",
"hl.dispatch": "fun(dispatcher: HL.Dispatcher|function): any",
"hl.define_submap": "fun(name: string, reset_or_fn: string|function, fn?: function): nil",
"hl.timer": "fun(callback: function, opts: HL.TimerOptions): HL.Timer",
"hl.config": "fun(config: table): nil",
@ -523,6 +524,9 @@ def generate_stub(root: Path) -> str:
lines.append("---@alias HL.CssGap integer|{top?:integer, right?:integer, bottom?:integer, left?:integer}")
lines.append("---@alias HL.Gradient string|{colors:string[], angle?:number}")
lines.append("")
lines.append("---@class HL.Dispatcher")
lines.append("local __HL_Dispatcher = {}")
lines.append("")
lines.extend(
emit_class_block(
@ -651,7 +655,8 @@ def generate_stub(root: Path) -> str:
for method in sorted(node.methods):
full_name = f"{full_prefix}.{method}"
method_type = api_signatures.get(full_name, "fun(...): any")
default_method_type = "fun(...): HL.Dispatcher" if path and path[0] == "dsp" else "fun(...): any"
method_type = api_signatures.get(full_name, default_method_type)
fields.append((method, method_type, False))
for child_name in sorted(node.children.keys()):

View file

@ -212,6 +212,7 @@
---| "group.groupbar.indicator_gap"
---| "group.groupbar.indicator_height"
---| "group.groupbar.keep_upper_gap"
---| "group.groupbar.middle_click_close"
---| "group.groupbar.priority"
---| "group.groupbar.render_titles"
---| "group.groupbar.round_only_edges"
@ -378,6 +379,9 @@
---@alias HL.CssGap integer|{top?:integer, right?:integer, bottom?:integer, left?:integer}
---@alias HL.Gradient string|{colors:string[], angle?:number}
---@class HL.Dispatcher
local __HL_Dispatcher = {}
---@class HL.Vec2
---@field x number
---@field y number
@ -485,6 +489,7 @@ local __HL_WindowQueryFilter = {}
---@field scroll_points? string
---@field sensitivity? number|boolean
---@field share_states? integer|boolean
---@field tags? string
---@field tap_and_drag? boolean
---@field tap_button_map? string
---@field tap_to_click? boolean
@ -565,12 +570,22 @@ local __HL_WorkspaceRuleSpec = {}
---@field remove fun(self: HL.EventSubscription, ...): any
local __HL_EventSubscription = {}
---@class HL.Group
---@field current HL.Window|nil
---@field current_index integer
---@field denied boolean
---@field locked boolean
---@field members HL.Window|table|nil
---@field size integer
local __HL_Group = {}
---@class HL.Keybind
---@field is_enabled fun(self: HL.Keybind, ...): any
---@field remove fun(self: HL.Keybind, ...): any
---@field set_enabled fun(self: HL.Keybind, ...): any
---@field unbind fun(self: HL.Keybind, ...): any
---@field arg string
---@field auto_consuming boolean
---@field catchall boolean
---@field click boolean
---@field description any
@ -667,6 +682,7 @@ local __HL_Notification = {}
local __HL_Timer = {}
---@class HL.Window
---@field accepts_input boolean
---@field active boolean|nil
---@field address string
---@field at integer|table
@ -676,7 +692,7 @@ local __HL_Timer = {}
---@field focus_history_id integer
---@field fullscreen integer
---@field fullscreen_client integer
---@field group HL.Window|boolean|integer|table|nil
---@field group HL.Group|nil
---@field hidden boolean
---@field inhibiting_idle boolean
---@field initial_class string
@ -692,6 +708,7 @@ local __HL_Timer = {}
---@field swallowing HL.Window|nil
---@field tags string|table
---@field title string
---@field visible boolean
---@field workspace HL.Workspace|nil
---@field xdg_description string|nil
---@field xdg_tag string|nil
@ -704,27 +721,35 @@ local __HL_Window = {}
local __HL_WindowRule = {}
---@class HL.Workspace
---@field get_groups fun(self: HL.Workspace, ...): any
---@field get_windows fun(self: HL.Workspace, ...): any
---@field active boolean
---@field config_name string
---@field fullscreen_mode integer
---@field fullscreen_window HL.Window|nil
---@field groups integer|nil
---@field has_fullscreen boolean
---@field has_urgent boolean
---@field id integer
---@field is_persistent boolean|nil
---@field is_empty boolean
---@field is_persistent boolean
---@field last_window HL.Window|nil
---@field monitor HL.Monitor|nil
---@field name string
---@field special boolean
---@field tiled_layout string
---@field visible boolean
---@field windows integer
local __HL_Workspace = {}
---@class HL.API
---@field animation fun(...): any
---@field bind fun(keys: string, dispatcher: function, opts?: HL.BindOptions): HL.Keybind
---@field bind fun(keys: string, dispatcher: HL.Dispatcher|function, opts?: HL.BindOptions): HL.Keybind
---@field config fun(config: table): nil
---@field curve fun(...): any
---@field define_submap fun(name: string, reset_or_fn: string|function, fn?: function): nil
---@field device fun(spec: HL.DeviceSpec): nil
---@field dispatch fun(...): any
---@field dispatch fun(dispatcher: HL.Dispatcher|function): any
---@field env fun(...): any
---@field exec_cmd fun(cmd: string, rules?: table<string, string|number|boolean>): nil
---@field gesture fun(spec: HL.GestureSpec): nil
@ -763,20 +788,21 @@ local __HL_Workspace = {}
local __HL_API = {}
---@class HL.DspNamespace
---@field dpms fun(...): any
---@field event fun(...): any
---@field exec_cmd fun(...): any
---@field exec_raw fun(...): any
---@field exit fun(...): any
---@field focus fun(...): any
---@field force_idle fun(...): any
---@field force_renderer_reload fun(...): any
---@field global fun(...): any
---@field layout fun(...): any
---@field pass fun(...): any
---@field send_key_state fun(...): any
---@field send_shortcut fun(...): any
---@field submap fun(...): any
---@field dpms fun(...): HL.Dispatcher
---@field event fun(...): HL.Dispatcher
---@field exec_cmd fun(...): HL.Dispatcher
---@field exec_raw fun(...): HL.Dispatcher
---@field exit fun(...): HL.Dispatcher
---@field focus fun(...): HL.Dispatcher
---@field force_idle fun(...): HL.Dispatcher
---@field force_renderer_reload fun(...): HL.Dispatcher
---@field global fun(...): HL.Dispatcher
---@field layout fun(...): HL.Dispatcher
---@field no_op fun(...): HL.Dispatcher
---@field pass fun(...): HL.Dispatcher
---@field send_key_state fun(...): HL.Dispatcher
---@field send_shortcut fun(...): HL.Dispatcher
---@field submap fun(...): HL.Dispatcher
---@field cursor HL.DspCursorNamespace
---@field group HL.DspGroupNamespace
---@field window HL.DspWindowNamespace
@ -784,48 +810,49 @@ local __HL_API = {}
local __HL_DspNamespace = {}
---@class HL.DspCursorNamespace
---@field move fun(...): any
---@field move_to_corner fun(...): any
---@field move fun(...): HL.Dispatcher
---@field move_to_corner fun(...): HL.Dispatcher
local __HL_DspCursorNamespace = {}
---@class HL.DspGroupNamespace
---@field active fun(...): any
---@field lock fun(...): any
---@field lock_active fun(...): any
---@field move_window fun(...): any
---@field next fun(...): any
---@field prev fun(...): any
---@field toggle fun(...): any
---@field active fun(...): HL.Dispatcher
---@field lock fun(...): HL.Dispatcher
---@field lock_active fun(...): HL.Dispatcher
---@field move_window fun(...): HL.Dispatcher
---@field next fun(...): HL.Dispatcher
---@field prev fun(...): HL.Dispatcher
---@field toggle fun(...): HL.Dispatcher
local __HL_DspGroupNamespace = {}
---@class HL.DspWindowNamespace
---@field alter_zorder fun(...): any
---@field bring_to_top fun(...): any
---@field center fun(...): any
---@field close fun(...): any
---@field cycle_next fun(...): any
---@field deny_from_group fun(...): any
---@field drag fun(...): any
---@field float fun(...): any
---@field fullscreen fun(...): any
---@field fullscreen_state fun(...): any
---@field kill fun(...): any
---@field move fun(...): any
---@field pin fun(...): any
---@field pseudo fun(...): any
---@field resize fun(...): any
---@field set_prop fun(...): any
---@field signal fun(...): any
---@field swap fun(...): any
---@field tag fun(...): any
---@field toggle_swallow fun(...): any
---@field alter_zorder fun(...): HL.Dispatcher
---@field bring_to_top fun(...): HL.Dispatcher
---@field center fun(...): HL.Dispatcher
---@field clear_tags fun(...): HL.Dispatcher
---@field close fun(...): HL.Dispatcher
---@field cycle_next fun(...): HL.Dispatcher
---@field deny_from_group fun(...): HL.Dispatcher
---@field drag fun(...): HL.Dispatcher
---@field float fun(...): HL.Dispatcher
---@field fullscreen fun(...): HL.Dispatcher
---@field fullscreen_state fun(...): HL.Dispatcher
---@field kill fun(...): HL.Dispatcher
---@field move fun(...): HL.Dispatcher
---@field pin fun(...): HL.Dispatcher
---@field pseudo fun(...): HL.Dispatcher
---@field resize fun(...): HL.Dispatcher
---@field set_prop fun(...): HL.Dispatcher
---@field signal fun(...): HL.Dispatcher
---@field swap fun(...): HL.Dispatcher
---@field tag fun(...): HL.Dispatcher
---@field toggle_swallow fun(...): HL.Dispatcher
local __HL_DspWindowNamespace = {}
---@class HL.DspWorkspaceNamespace
---@field move fun(...): any
---@field rename fun(...): any
---@field swap_monitors fun(...): any
---@field toggle_special fun(...): any
---@field move fun(...): HL.Dispatcher
---@field rename fun(...): HL.Dispatcher
---@field swap_monitors fun(...): HL.Dispatcher
---@field toggle_special fun(...): HL.Dispatcher
local __HL_DspWorkspaceNamespace = {}
---@class HL.NotificationNamespace
@ -1021,6 +1048,7 @@ hl = {}
---@field ['group.groupbar.indicator_gap'] integer|boolean
---@field ['group.groupbar.indicator_height'] integer|boolean
---@field ['group.groupbar.keep_upper_gap'] boolean
---@field ['group.groupbar.middle_click_close'] boolean
---@field ['group.groupbar.priority'] integer|boolean
---@field ['group.groupbar.render_titles'] boolean
---@field ['group.groupbar.round_only_edges'] boolean

View file

@ -101,6 +101,7 @@ in
machine.copy_from_vm("/tmp/testerlog")
machine.copy_from_vm("/tmp/hyprlog")
machine.copy_from_vm("/tmp/exit_status")
machine.copy_from_vm("/tmp/exit_status_gtests")
# Finally - shutdown
machine.shutdown()

View file

@ -10,6 +10,7 @@
#include "desktop/history/WorkspaceHistoryTracker.hpp"
#include "desktop/view/Group.hpp"
#include "helpers/Splashes.hpp"
#include "helpers/SystemInfo.hpp"
#include "config/ConfigValue.hpp"
#include "config/legacy/ConfigManager.hpp"
#include "config/shared/inotify/ConfigWatcher.hpp"
@ -99,6 +100,7 @@ using namespace Hyprutils::String;
using namespace Aquamarine;
using enum NContentType::eContentType;
using namespace NColorManagement;
using namespace Desktop::View;
using namespace Render::GL;
static int handleCritSignal(int signo, void* data) {
@ -231,26 +233,7 @@ CCompositor::CCompositor(bool onlyConfig) : m_onlyConfigVerification(onlyConfig)
Log::logger->initIS(m_instancePath);
Log::logger->log(Log::DEBUG, "Instance Signature: {}", m_instanceSignature);
Log::logger->log(Log::DEBUG, "Runtime directory: {}", m_instancePath);
Log::logger->log(Log::DEBUG, "Hyprland PID: {}", m_hyprlandPID);
Log::logger->log(Log::DEBUG, "===== SYSTEM INFO: =====");
logSystemInfo();
Log::logger->log(Log::DEBUG, "========================");
Log::logger->log(Log::DEBUG, "\n\n"); // pad
Log::logger->log(Log::INFO, "If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\n\n");
setRandomSplash();
Log::logger->log(Log::DEBUG, "\nCurrent splash: {}\n\n", m_currentSplash);
bumpNofile();
}
@ -364,6 +347,16 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
m_initialized = true;
Log::logger->log(Log::DEBUG, "Instance Signature: {}", m_instanceSignature);
Log::logger->log(Log::DEBUG, "Runtime directory: {}", m_instancePath);
Log::logger->log(Log::DEBUG, "Hyprland PID: {}", m_hyprlandPID);
Log::logger->log(Log::DEBUG, "===== SYSTEM INFO: =====");
Log::logger->log(Log::DEBUG, "{}", Helpers::SystemInfo::getSystemInfo());
Log::logger->log(Log::DEBUG, "========================");
Log::logger->log(Log::DEBUG, "\n\n"); // pad
Log::logger->log(Log::INFO, "If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\n\n");
Log::logger->log(Log::DEBUG, "\nCurrent splash: {}\n\n", m_currentSplash);
m_drm.fd = m_aqBackend->drmFD();
Log::logger->log(Log::DEBUG, "Running on DRMFD: {}", m_drm.fd);
@ -680,6 +673,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Log::logger->log(Log::DEBUG, "Creating the SeatManager!");
g_pSeatManager = makeUnique<CSeatManager>();
Log::logger->log(Log::DEBUG, "Creating the SessionLockManager!");
g_pSessionLockManager = makeUnique<CSessionLockManager>();
// init focus state els
Desktop::History::windowTracker();
Desktop::History::workspaceTracker();
@ -695,9 +691,6 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Log::logger->log(Log::DEBUG, "Creating the XWaylandManager!");
g_pXWaylandManager = makeUnique<CHyprXWaylandManager>();
Log::logger->log(Log::DEBUG, "Creating the SessionLockManager!");
g_pSessionLockManager = makeUnique<CSessionLockManager>();
Log::logger->log(Log::DEBUG, "Creating the Debug Overlay!");
Debug::overlay();
@ -926,7 +919,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t prope
if (ONLY_PRIORITY && !w->priorityFocus())
continue;
if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
if (w->m_isFloating && w->m_isMapped && w->acceptsInput() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
w != pIgnoreWindow && !isShadowedByModal(w)) {
const auto BB = w->getWindowBoxUnified(properties);
CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0);
@ -966,8 +959,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t prope
continue;
}
if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
w != pIgnoreWindow && (!aboveFullscreen || w->m_createdOverFullscreen) && !isShadowedByModal(w)) {
if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && w->acceptsInput() && !w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
w != pIgnoreWindow && (!aboveFullscreen || w->isAllowedOverFullscreen()) && !isShadowedByModal(w)) {
// OR windows should add focus to parent
if (w->m_X11ShouldntFocus && !w->isX11OverrideRedirect())
continue;
@ -1038,7 +1031,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t prope
if (!w->m_workspace)
continue;
if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus &&
if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && w->acceptsInput() && !w->m_X11ShouldntFocus &&
!w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) {
if (w->hasPopupAt(pos))
return w;
@ -1055,7 +1048,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t prope
if (!w->m_workspace)
continue;
if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && w->acceptsInput() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
w != pIgnoreWindow && !isShadowedByModal(w)) {
CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size};
if ((properties & Desktop::View::INPUT_EXTENTS) && BORDER_GRAB_AREA > 0 && !w->isX11OverrideRedirect()) {
@ -1309,6 +1302,9 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) {
else
pWindow->m_createdOverFullscreen = false;
pWindow->updateFullscreenInputState();
*pWindow->alpha(WINDOW_ALPHA_FULLSCREEN) = pWindow->isBlockedByFullscreen() ? 0.F : 1.F;
if (pWindow == (top ? m_windows.back() : m_windows.front()))
return;
@ -1368,7 +1364,7 @@ void CCompositor::cleanupFadingOut(const MONITORID& monid) {
if (w->monitorID() != monid && w->m_monitor)
continue;
if (!w->m_fadingOut || w->m_alpha->value() == 0.f) {
if (!w->m_fadingOut || w->alphaValue(WINDOW_ALPHA_FADE) == 0.f) {
w->m_fadingOut = false;
@ -1514,13 +1510,13 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
};
for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || !w->acceptsInput() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue;
if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)
continue;
if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen)
if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen())
continue;
if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)
@ -1593,13 +1589,13 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
constexpr float THRESHOLD = 0.3 * M_PI;
for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_isMapped || !w->m_workspace || w->isHidden() || (!w->isFullscreen() && !w->m_isFloating) || !w->m_workspace->isVisible())
if (w == ignoreWindow || !w->m_isMapped || !w->m_workspace || !w->acceptsInput() || (!w->isFullscreen() && !w->m_isFloating) || !w->m_workspace->isVisible())
continue;
if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace)
continue;
if (pWorkspace->m_hasFullscreenWindow && !w->isFullscreen() && !w->m_createdOverFullscreen)
if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen())
continue;
if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor)
@ -1639,9 +1635,19 @@ static bool isFloatingMatches(WINDOWPTR w, std::optional<bool> floating) {
}
template <typename WINDOWPTR>
static bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional<bool> floating, bool anyWorkspace = false) {
static bool acceptsInputForCycle(WINDOWPTR w, bool allowFullscreenBlocked) {
if (w->acceptsInput())
return true;
return allowFullscreenBlocked && !w->isHidden() && w->isInputBlockedOnly(INPUT_BLOCK_BELOW_FULLSCREEN);
}
template <typename WINDOWPTR>
static bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional<bool> floating, bool anyWorkspace = false,
bool allowFullscreenBlocked = false) {
return isFloatingMatches(w, floating) &&
(w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_ruleApplicator->noFocus().valueOrDefault()));
(w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && acceptsInputForCycle(w, allowFullscreenBlocked) &&
(!focusableOnly || !w->m_ruleApplicator->noFocus().valueOrDefault()));
}
template <typename Iterator>
@ -1662,16 +1668,16 @@ static PHLWINDOW getWeakWindowPred(Iterator cur, Iterator end, Iterator begin, c
return IN_OTHER_SIDE->lock();
}
PHLWINDOW CCompositor::getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool next) {
const auto FINDER = [&](const PHLWINDOWREF& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); };
PHLWINDOW CCompositor::getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool next, bool allowFullscreenBlocked) {
const auto FINDER = [&](const PHLWINDOWREF& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible, allowFullscreenBlocked); };
// also m_vWindowFocusHistory has reverse order, so when it is next - we need to reverse again
const auto& HISTORY = Desktop::History::windowTracker()->fullHistory();
return next ? getWeakWindowPred(std::ranges::find(HISTORY, cur), HISTORY.end(), HISTORY.begin(), FINDER) :
getWeakWindowPred(std::ranges::find(HISTORY | std::views::reverse, cur), HISTORY.rend(), HISTORY.rbegin(), FINDER);
}
PHLWINDOW CCompositor::getWindowCycle(PHLWINDOW cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool prev) {
const auto FINDER = [&](const PHLWINDOW& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); };
PHLWINDOW CCompositor::getWindowCycle(PHLWINDOW cur, bool focusableOnly, std::optional<bool> floating, bool visible, bool prev, bool allowFullscreenBlocked) {
const auto FINDER = [&](const PHLWINDOW& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible, allowFullscreenBlocked); };
return prev ? getWindowPred(std::ranges::find(m_windows | std::views::reverse, cur), m_windows.rend(), m_windows.rbegin(), FINDER) :
getWindowPred(std::ranges::find(m_windows, cur), m_windows.end(), m_windows.begin(), FINDER);
}
@ -1685,7 +1691,7 @@ WORKSPACEID CCompositor::getNextAvailableNamedWorkspace() {
// Give priority to persistent workspaces to avoid any conflicts between them.
for (auto const& rule : Config::workspaceRuleMgr()->getAllWorkspaceRules()) {
if (!rule.m_isPersistent)
if (!rule.m_isPersistent.value_or(false))
continue;
if (rule.m_workspaceId < -1 && rule.m_workspaceId < lowest)
lowest = rule.m_workspaceId;
@ -2257,8 +2263,12 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
// make all windows and layers on the same workspace under the fullscreen window
for (auto const& w : m_windows) {
if (w->m_workspace == PWORKSPACE && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned)
w->m_createdOverFullscreen = false;
if (w->m_workspace == PWORKSPACE) {
if (!w->isFullscreen() && !w->m_fadingOut && !w->m_pinned)
w->m_createdOverFullscreen = false;
w->updateFullscreenInputState();
}
}
for (auto const& ls : m_layers) {
if (ls->m_monitor == PMONITOR)
@ -2330,7 +2340,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
const bool FLOAT = regexp.starts_with("floating");
for (auto const& w : m_windows) {
if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != Desktop::focusState()->window()->m_workspace || w->isHidden())
if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != Desktop::focusState()->window()->m_workspace || !w->acceptsInput())
continue;
return w;
@ -2700,14 +2710,14 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
g_pCompositor->updateSuspendedStates();
if (!WASVISIBLE && pWindow->m_workspace && pWindow->m_workspace->isVisible()) {
pWindow->m_movingFromWorkspaceAlpha->setValueAndWarp(0.F);
*pWindow->m_movingFromWorkspaceAlpha = 1.F;
pWindow->alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE)->setValueAndWarp(0.F);
*pWindow->alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE) = 1.F;
}
}
PHLWINDOW CCompositor::getForceFocus() {
for (auto const& w : m_windows) {
if (!w->m_isMapped || w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible())
if (!w->m_isMapped || !w->acceptsInput() || !w->m_workspace || !w->m_workspace->isVisible())
continue;
if (!w->m_ruleApplicator->stayFocused().valueOrDefault())
@ -3078,7 +3088,7 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector<Config::CW
std::vector<PHLWORKSPACE> persistentFound;
for (const auto& rule : rules) {
if (!rule.m_isPersistent)
if (!rule.m_isPersistent.value_or(false))
continue;
PHLWORKSPACE PWORKSPACE = nullptr;

View file

@ -117,8 +117,10 @@ class CCompositor {
void cleanupFadingOut(const MONITORID& monid);
PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection);
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false,
bool allowFullscreenBlocked = false);
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false,
bool allowFullscreenBlocked = false);
WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);

View file

@ -555,6 +555,7 @@ CConfigManager::CConfigManager() {
m_config->addSpecialConfigValue("device", "keybinds", Hyprlang::INT{1}); // enable/disable keybinds
m_config->addSpecialConfigValue("device", "share_states", Hyprlang::INT{0}); // only for virtualkeyboards
m_config->addSpecialConfigValue("device", "release_pressed_on_close", Hyprlang::INT{0}); // only for virtualkeyboards
m_config->addSpecialConfigValue("device", "tags", STRVAL_EMPTY); // only for keyboards and mice
m_config->addSpecialCategory("monitorv2", {.key = "output"});
m_config->addSpecialConfigValue("monitorv2", "disabled", Hyprlang::INT{0});
@ -1155,6 +1156,15 @@ std::string CConfigManager::getDeviceString(const std::string& dev, const std::s
}
SConfigOptionReply CConfigManager::getConfigValue(const std::string& val) {
if (val.starts_with("plugin:")) {
const auto VAL = m_config->getSpecialConfigValuePtr("plugin", val.substr(7).c_str(), nullptr);
if (!VAL)
return {};
return {.dataptr = VAL->getDataStaticPtr(), .type = &VAL->getValue().type(), .setByUser = VAL->m_bSetByUser};
}
const auto VAL = m_config->getConfigValuePtr(val.c_str());
if (!VAL)
return {};
@ -1478,6 +1488,7 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
bool repeat = false;
bool mouse = false;
bool nonConsuming = false;
bool autoConsuming = false;
bool transparent = false;
bool ignoreMods = false;
bool multiKey = false;
@ -1497,6 +1508,7 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
case 'e': repeat = true; break;
case 'm': mouse = true; break;
case 'n': nonConsuming = true; break;
case 'a': autoConsuming = true; break;
case 't': transparent = true; break;
case 'i': ignoreMods = true; break;
case 's': multiKey = true; break;
@ -1536,15 +1548,15 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
else if ((ARGS.size() > sc<size_t>(4) + DESCR_OFFSET + DEVICE_OFFSET && !mouse) || (ARGS.size() > sc<size_t>(3) + DESCR_OFFSET + DEVICE_OFFSET && mouse))
return "bind: too many args";
std::vector<xkb_keysym_t> KEYSYMS;
std::vector<xkb_keysym_t> MODS;
std::vector<KeybindKey> KEYSYMS;
std::vector<KeybindKey> MODS;
if (multiKey) {
for (const auto& splitKey : CVarList(ARGS[1], 8, '&')) {
KEYSYMS.emplace_back(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
KEYSYMS.emplace_back(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE), 0);
}
for (const auto& splitMod : CVarList(ARGS[0], 8, '&')) {
MODS.emplace_back(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
MODS.emplace_back(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE), 0);
}
}
const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]);
@ -1596,10 +1608,33 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
return "Invalid catchall, catchall keybinds are only allowed in submaps.";
}
g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS, parsedKey.keycode, parsedKey.catchAll, MOD, MODS, HANDLER,
COMMAND, locked, m_currentSubmap, DESCRIPTION, release, repeat, longPress,
mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit,
click, drag, submapUniversal, deviceInclusive, devices});
g_pKeybindManager->addKeybind(SKeybind{parsedKey.key,
KEYSYMS,
parsedKey.keycode,
parsedKey.catchAll,
MOD,
MODS,
HANDLER,
COMMAND,
locked,
m_currentSubmap,
DESCRIPTION,
release,
repeat,
longPress,
mouse,
nonConsuming,
autoConsuming,
transparent,
ignoreMods,
multiKey,
hasDescription,
dontInhibit,
click,
drag,
submapUniversal,
deviceInclusive,
devices});
}
return {};

View file

@ -160,7 +160,7 @@ static int safeLuaRequire(lua_State* L) {
WP<CConfigManager> Lua::mgr() {
auto& mgr = Config::mgr();
if (mgr->type() != CONFIG_LUA)
if (!mgr || mgr->type() != CONFIG_LUA)
return nullptr;
return dynamicPointerCast<Lua::CConfigManager>(WP<IConfigManager>(mgr));

View file

@ -2,10 +2,10 @@
#include "ConfigManager.hpp"
#include "objects/LuaWindow.hpp"
#include "objects/LuaWorkspace.hpp"
#include "objects/LuaGroup.hpp"
#include "objects/LuaMonitor.hpp"
#include "objects/LuaLayerSurface.hpp"
#include "../../defines.hpp"
#include "../../event/EventBus.hpp"
#include "../../desktop/state/FocusState.hpp"
@ -14,7 +14,6 @@ extern "C" {
}
#include <format>
#include <algorithm>
using namespace Config::Lua;
using namespace Config::Lua::Objects;
@ -62,15 +61,18 @@ void CLuaEventHandler::dispatch(const std::string& name, int nargs, const std::f
lua_rawgeti(m_lua, LUA_REGISTRYINDEX, sub->second.luaRef);
pushArgs();
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(m_lua); mgr)
auto* mgr = CConfigManager::fromLuaState(m_lua);
int status = LUA_OK;
if (mgr)
status = mgr->guardedPCall(nargs, 0, 0, CConfigManager::LUA_TIMEOUT_EVENT_CALLBACK_MS, std::format("hl.on(\"{}\") callback", name));
else
status = lua_pcall(m_lua, nargs, 0, 0);
if (status != LUA_OK) {
const char* err = lua_tostring(m_lua, -1);
Config::Lua::mgr()->addError(std::format("hl.on(\"{}\") callback: {}", name, err ? err : "(unknown)"));
if (mgr)
mgr->addError(std::format("hl.on(\"{}\") callback: {}", name, err ? err : "(unknown)"));
lua_pop(m_lua, 1);
}
}
@ -78,6 +80,7 @@ void CLuaEventHandler::dispatch(const std::string& name, int nargs, const std::f
CLuaEventHandler::CLuaEventHandler(lua_State* L) : m_lua(L) {
CLuaWindow{}.setup(L);
Objects::CLuaGroup{}.setup(L);
CLuaWorkspace{}.setup(L);
CLuaMonitor{}.setup(L);
CLuaLayerSurface{}.setup(L);

View file

@ -40,9 +40,12 @@
#include "../../../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp"
#include "../../../managers/permissions/DynamicPermissionManager.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
using namespace Hyprutils::Utils;
namespace {
struct SFieldDesc {
@ -262,6 +265,7 @@ namespace {
{"keybinds", []() -> ILuaConfigValue* { return new CLuaConfigBool(true); }},
{"share_states", []() -> ILuaConfigValue* { return new CLuaConfigInt(0, 0, 2); }},
{"release_pressed_on_close", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }},
{"tags", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }},
};
}
@ -286,53 +290,101 @@ static int hlCurve(lua_State* L) {
const auto& curveType = typeParser.parsed();
if (curveType != "bezier")
return Internal::configError(L, std::format("hl.curve(\"{}\"): unknown curve type \"{}\", expected \"bezier\"", name, curveType));
lua_getfield(L, 2, "points");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return Internal::configError(L, std::format("hl.curve(\"{}\"): missing or invalid \"points\" field, expected a table of two points", name));
}
int pointsIdx = lua_gettop(L);
if (luaL_len(L, pointsIdx) != 2) {
lua_pop(L, 1);
return Internal::configError(L, std::format("hl.curve(\"{}\"): \"points\" must contain exactly 2 points, e.g. {{ {{0, 0}}, {{1, 1}} }}", name));
}
float coords[4] = {};
for (int pt = 1; pt <= 2; pt++) {
lua_rawgeti(L, pointsIdx, pt);
if (!lua_istable(L, -1) || luaL_len(L, -1) != 2) {
lua_pop(L, 2);
return Internal::configError(L, std::format("hl.curve(\"{}\"): point {} must be a table of 2 numbers, e.g. {{0.25, 0.1}}", name, pt));
}
int ptIdx = lua_gettop(L);
for (int comp = 0; comp < 2; comp++) {
lua_rawgeti(L, ptIdx, comp + 1);
CLuaConfigFloat coordParser(0.F, -1.F, 2.F);
auto coordErr = coordParser.parse(L);
if (curveType == "bezier") {
lua_getfield(L, 2, "points");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
if (coordErr.errorCode != PARSE_ERROR_OK) {
lua_pop(L, 2);
return Internal::configError(L, std::format("hl.curve(\"{}\"): point {}[{}]: {}", name, pt, comp + 1, coordErr.message));
}
coords[((pt - 1) * 2) + comp] = coordParser.parsed();
return Internal::configError(L, std::format("hl.curve(\"{}\"): missing or invalid \"points\" field, expected a table of two points", name));
}
int pointsIdx = lua_gettop(L);
if (luaL_len(L, pointsIdx) != 2) {
lua_pop(L, 1);
return Internal::configError(L, std::format("hl.curve(\"{}\"): \"points\" must contain exactly 2 points, e.g. {{ {{0, 0}}, {{1, 1}} }}", name));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
float coords[4] = {};
for (int pt = 1; pt <= 2; pt++) {
lua_rawgeti(L, pointsIdx, pt);
if (!lua_istable(L, -1) || luaL_len(L, -1) != 2) {
lua_pop(L, 2);
return Internal::configError(L, std::format("hl.curve(\"{}\"): point {} must be a table of 2 numbers, e.g. {{0.25, 0.1}}", name, pt));
}
int ptIdx = lua_gettop(L);
for (int comp = 0; comp < 2; comp++) {
lua_rawgeti(L, ptIdx, comp + 1);
CLuaConfigFloat coordParser(0.F, -1.F, 2.F);
auto coordErr = coordParser.parse(L);
lua_pop(L, 1);
if (coordErr.errorCode != PARSE_ERROR_OK) {
lua_pop(L, 2);
return Internal::configError(L, std::format("hl.curve(\"{}\"): point {}[{}]: {}", name, pt, comp + 1, coordErr.message));
}
coords[((pt - 1) * 2) + comp] = coordParser.parsed();
}
lua_pop(L, 1);
}
lua_pop(L, 1);
g_pAnimationManager->addBezierWithName(name, Vector2D(coords[0], coords[1]), Vector2D(coords[2], coords[3]));
} else if (curveType == "spring") {
Hyprutils::Animation::SSpringCurve curve;
{
CScopeGuard x([L] { lua_pop(L, 1); });
lua_getfield(L, 2, "stiffness");
if (!lua_isnumber(L, -1))
return Internal::configError(L, std::format("hl.curve(\"{}\"): stiffness expects a number", name));
curve.stiffness = lua_tonumber(L, -1);
if (curve.stiffness <= 0.5F)
return Internal::configError(L, std::format("hl.curve(\"{}\"): stiffness expects a number >= 0.5", name));
}
{
CScopeGuard x([L] { lua_pop(L, 1); });
lua_getfield(L, 2, "dampening");
if (!lua_isnumber(L, -1))
return Internal::configError(L, std::format("hl.curve(\"{}\"): dampening expects a number", name));
curve.damping = lua_tonumber(L, -1);
if (curve.damping <= 0.5F)
return Internal::configError(L, std::format("hl.curve(\"{}\"): dampening expects a number >= 0.5", name));
}
{
CScopeGuard x([L] { lua_pop(L, 1); });
lua_getfield(L, 2, "mass");
if (!lua_isnumber(L, -1))
return Internal::configError(L, std::format("hl.curve(\"{}\"): mass expects a number", name));
curve.mass = lua_tonumber(L, -1);
if (curve.mass <= 0.5F)
return Internal::configError(L, std::format("hl.curve(\"{}\"): mass expects a number >= 0.5", name));
}
g_pAnimationManager->addSpringWithName(name, curve);
} else
return Internal::configError(L, std::format(R"(hl.curve("{}"): unknown curve type "{}", expected "bezier" or "spring")", name, curveType));
g_pAnimationManager->addBezierWithName(name, Vector2D(coords[0], coords[1]), Vector2D(coords[2], coords[3]));
return 0;
}
static int hlAnimation(lua_State* L) {
if (!lua_istable(L, 1))
return Internal::configError(L, "hl.animation: expected a table, e.g. { leaf = \"global\", enabled = true, speed = 5, bezier = \"default\" }");
return Internal::configError(L, R"(hl.animation: expected a table, e.g. { leaf = "global", enabled = true, speed = 5, bezier = "default" })");
CLuaConfigString leafParser("");
auto leafErr = Internal::parseTableField(L, 1, "leaf", leafParser);
@ -366,15 +418,34 @@ static int hlAnimation(lua_State* L) {
if (speed <= 0)
return Internal::configError(L, std::format("hl.animation(\"{}\"): speed must be greater than 0", leaf));
CLuaConfigString bezierParser("");
auto bezierErr = Internal::parseTableField(L, 1, "bezier", bezierParser);
if (bezierErr.errorCode != PARSE_ERROR_OK)
return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, bezierErr.message));
std::string curveName;
const auto& bezierName = bezierParser.parsed();
if (Internal::hasTableField(L, 1, "bezier")) {
CLuaConfigString bezierParser("");
auto bezierErr = Internal::parseTableField(L, 1, "bezier", bezierParser);
if (bezierErr.errorCode != PARSE_ERROR_OK)
return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, bezierErr.message));
if (!g_pAnimationManager->bezierExists(bezierName))
return Internal::configError(L, std::format("hl.animation(\"{}\"): no such bezier \"{}\"", leaf, bezierName));
const auto& bezierName = bezierParser.parsed();
if (!g_pAnimationManager->bezierExists(bezierName))
return Internal::configError(L, std::format(R"(hl.animation("{}"): no such bezier "{}")", leaf, bezierName));
curveName = bezierName;
} else if (Internal::hasTableField(L, 1, "spring")) {
CLuaConfigString springParser("");
auto springErr = Internal::parseTableField(L, 1, "spring", springParser);
if (springErr.errorCode != PARSE_ERROR_OK)
return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, springErr.message));
const auto& springName = springParser.parsed();
if (!g_pAnimationManager->springExists(springName))
return Internal::configError(L, std::format(R"(hl.animation("{}"): no such spring "{}")", leaf, springName));
curveName = "spring:" + springName;
} else
return Internal::configError(L, std::format(R"(hl.animation("{}"): bezier or spring is required)", leaf));
std::string style;
lua_getfield(L, 1, "style");
@ -383,7 +454,7 @@ static int hlAnimation(lua_State* L) {
auto styleErr = styleParser.parse(L);
if (styleErr.errorCode != PARSE_ERROR_OK) {
lua_pop(L, 1);
return Internal::configError(L, std::format("hl.animation(\"{}\"): field \"style\": {}", leaf, styleErr.message));
return Internal::configError(L, std::format(R"(hl.animation("{}"): field "style": {})", leaf, styleErr.message));
}
style = styleParser.parsed();
}
@ -395,7 +466,7 @@ static int hlAnimation(lua_State* L) {
return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, err));
}
Config::animationTree()->setConfigForNode(leaf, true, speed, bezierName, style);
Config::animationTree()->setConfigForNode(leaf, true, speed, curveName, style);
return 0;
}

View file

@ -0,0 +1,144 @@
#include "LuaBindingsInternal.hpp"
using namespace Config::Lua::Bindings;
static constexpr const char* DISPATCHER_MT = "HL.Dispatcher";
static char DISPATCHER_TABLES_REGISTRY_KEY;
namespace {
struct SDispatcherRef {
int ref = LUA_NOREF;
};
}
static int dispatcherGc(lua_State* L) {
auto* dispatcher = sc<SDispatcherRef*>(luaL_checkudata(L, 1, DISPATCHER_MT));
if (dispatcher->ref != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, dispatcher->ref);
dispatcher->ref = LUA_NOREF;
}
return 0;
}
static int dispatcherCall(lua_State* L) {
return Internal::configError(L, "dispatcher objects cannot be called directly; use hl.dispatch(dispatcher)");
}
static int dispatcherToString(lua_State* L) {
lua_pushstring(L, "HL.Dispatcher");
return 1;
}
static void ensureDispatcherMetatable(lua_State* L) {
if (luaL_newmetatable(L, DISPATCHER_MT)) {
lua_pushcfunction(L, dispatcherGc);
lua_setfield(L, -2, "__gc");
lua_pushcfunction(L, dispatcherCall);
lua_setfield(L, -2, "__call");
lua_pushcfunction(L, dispatcherToString);
lua_setfield(L, -2, "__tostring");
lua_pushstring(L, DISPATCHER_MT);
lua_setfield(L, -2, "__metatable");
}
lua_pop(L, 1);
}
static bool isDispatcherTable(lua_State* L, int idx) {
if (!lua_istable(L, idx))
return false;
idx = lua_absindex(L, idx);
lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
lua_rawget(L, LUA_REGISTRYINDEX);
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return false;
}
lua_pushvalue(L, idx);
lua_rawget(L, -2);
const bool result = lua_toboolean(L, -1);
lua_pop(L, 2);
return result;
}
static int dispatcherFactory(lua_State* L) {
const int nargs = lua_gettop(L);
lua_pushvalue(L, lua_upvalueindex(1));
lua_insert(L, 1);
lua_call(L, nargs, LUA_MULTRET);
const int nresults = lua_gettop(L);
if (nresults == 1 && lua_isfunction(L, -1))
return Internal::wrapDispatcher(L);
return nresults;
}
void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
if (isDispatcherTable(L, -1)) {
lua_pushcfunction(L, fn);
lua_pushcclosure(L, dispatcherFactory, 1);
} else
lua_pushcfunction(L, fn);
lua_setfield(L, -2, name);
}
void Internal::markDispatcherTable(lua_State* L) {
if (!lua_istable(L, -1))
return;
lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
lua_rawget(L, LUA_REGISTRYINDEX);
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_pushlightuserdata(L, &DISPATCHER_TABLES_REGISTRY_KEY);
lua_pushvalue(L, -2);
lua_rawset(L, LUA_REGISTRYINDEX);
}
lua_pushvalue(L, -2);
lua_pushboolean(L, true);
lua_rawset(L, -3);
lua_pop(L, 1);
}
int Internal::wrapDispatcher(lua_State* L) {
luaL_checktype(L, -1, LUA_TFUNCTION);
const int ref = luaL_ref(L, LUA_REGISTRYINDEX);
new (lua_newuserdata(L, sizeof(SDispatcherRef))) SDispatcherRef{.ref = ref};
ensureDispatcherMetatable(L);
luaL_getmetatable(L, DISPATCHER_MT);
lua_setmetatable(L, -2);
return 1;
}
bool Internal::pushDispatcherFunction(lua_State* L, int idx) {
if (lua_isfunction(L, idx)) {
lua_pushvalue(L, idx);
return true;
}
auto* dispatcher = sc<SDispatcherRef*>(luaL_testudata(L, idx, DISPATCHER_MT));
if (!dispatcher || dispatcher->ref == LUA_NOREF)
return false;
lua_rawgeti(L, LUA_REGISTRYINDEX, dispatcher->ref);
if (lua_isfunction(L, -1))
return true;
lua_pop(L, 1);
return false;
}

View file

@ -563,6 +563,10 @@ static int dsp_tagWindow(lua_State* L) {
return Internal::checkResult(L, CA::tag(lua_tostring(L, lua_upvalueindex(1)), Internal::windowFromUpval(L, 2)));
}
static int dsp_clearTags(lua_State* L) {
return Internal::checkResult(L, CA::clearTags(Internal::windowFromUpval(L, 1)));
}
static int dsp_toggleSwallow(lua_State* L) {
return Internal::checkResult(L, CA::toggleSwallow());
}
@ -915,6 +919,12 @@ static int hlWindowTag(lua_State* L) {
return 1;
}
static int hlWindowClearTags(lua_State* L) {
Internal::pushWindowUpval(L, 1);
lua_pushcclosure(L, dsp_clearTags, 1);
return 1;
}
static int hlWindowToggleSwallow(lua_State* L) {
lua_pushcclosure(L, dsp_toggleSwallow, 0);
return 1;
@ -1093,6 +1103,15 @@ static int hlFocus(lua_State* L) {
return Internal::configError(L, "hl.focus: unrecognized arguments. Expected one of: direction, monitor, window, urgent_or_last, last");
}
static int dsp_noop(lua_State* L) {
return 0;
}
static int hlNoop(lua_State* L) {
lua_pushcclosure(L, dsp_noop, 0);
return 1;
}
static int dsp_toggleSpecial(lua_State* L) {
std::string name = lua_isnil(L, lua_upvalueindex(1)) ? "" : lua_tostring(L, lua_upvalueindex(1));
const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString("special:" + name);
@ -1158,9 +1177,9 @@ static int hlWorkspaceToggleSpecial(lua_State* L) {
static int hlWorkspaceRename(lua_State* L) {
if (!lua_istable(L, 1))
return Internal::configError(L, "hl.workspace.rename: expected a table { id, name? }");
return Internal::configError(L, "hl.workspace.rename: expected a table { workspace, name? }");
const auto id = Internal::requireTableFieldWorkspaceSelector(L, 1, "id", "hl.workspace.rename");
const auto id = Internal::requireTableFieldWorkspaceSelector(L, 1, "workspace", "hl.workspace.rename");
auto name = Internal::tableOptStr(L, 1, "name");
lua_pushstring(L, id.c_str());
@ -1178,7 +1197,7 @@ static int hlWorkspaceMove(lua_State* L) {
const auto mon = Internal::requireTableFieldMonitorSelector(L, 1, "monitor", "hl.workspace.move");
auto id = Internal::tableOptWorkspaceSelector(L, 1, "id", "hl.workspace.move");
auto id = Internal::tableOptWorkspaceSelector(L, 1, "workspace", "hl.workspace.move");
if (id) {
lua_pushstring(L, id->c_str());
lua_pushstring(L, mon.c_str());
@ -1205,14 +1224,17 @@ static int hlWorkspaceSwapMonitors(lua_State* L) {
void Internal::registerDispatcherBindings(lua_State* L) {
lua_newtable(L);
Internal::markDispatcherTable(L);
{
lua_newtable(L);
Internal::markDispatcherTable(L);
Internal::setFn(L, "move_to_corner", hlCursorMoveToCorner);
Internal::setFn(L, "move", hlCursorMove);
lua_setfield(L, -2, "cursor");
lua_newtable(L);
Internal::markDispatcherTable(L);
Internal::setFn(L, "toggle", hlGroupToggle);
Internal::setFn(L, "next", hlGroupNext);
Internal::setFn(L, "prev", hlGroupPrev);
@ -1223,6 +1245,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
lua_setfield(L, -2, "group");
lua_newtable(L);
Internal::markDispatcherTable(L);
Internal::setFn(L, "close", hlWindowClose);
Internal::setFn(L, "kill", hlWindowKill);
Internal::setFn(L, "signal", hlWindowSignal);
@ -1235,6 +1258,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
Internal::setFn(L, "center", hlWindowCenter);
Internal::setFn(L, "cycle_next", hlWindowCycleNext);
Internal::setFn(L, "tag", hlWindowTag);
Internal::setFn(L, "clear_tags", hlWindowClearTags);
Internal::setFn(L, "toggle_swallow", hlWindowToggleSwallow);
Internal::setFn(L, "pin", hlWindowPin);
Internal::setFn(L, "bring_to_top", hlWindowBringToTop);
@ -1246,6 +1270,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
lua_setfield(L, -2, "window");
lua_newtable(L);
Internal::markDispatcherTable(L);
Internal::setFn(L, "rename", hlWorkspaceRename);
Internal::setFn(L, "move", hlWorkspaceMove);
Internal::setFn(L, "swap_monitors", hlWorkspaceSwapMonitors);
@ -1266,6 +1291,7 @@ void Internal::registerDispatcherBindings(lua_State* L) {
Internal::setFn(L, "force_renderer_reload", hlForceRendererReload);
Internal::setFn(L, "force_idle", hlForceIdle);
Internal::setFn(L, "focus", hlFocus);
Internal::setFn(L, "no_op", hlNoop);
}
lua_setfield(L, -2, "dsp");

View file

@ -449,11 +449,6 @@ CA::eTogglableAction Internal::tableToggleAction(lua_State* L, int idx, const ch
return CA::TOGGLE_ACTION_TOGGLE;
}
void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
lua_pushcfunction(L, fn);
lua_setfield(L, -2, name);
}
void Internal::setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn) {
lua_pushlightuserdata(L, mgr);
lua_pushcclosure(L, fn, 1);
@ -574,3 +569,14 @@ std::expected<SP<Desktop::Rule::CWindowRule>, int> Internal::buildRuleFromTable(
return rule;
}
bool Internal::hasTableField(lua_State* L, int tableIdx, const char* field) {
lua_getfield(L, tableIdx, field);
if (lua_isnoneornil(L, -1)) {
lua_pop(L, 1);
return false;
}
lua_pop(L, 1);
return true;
}

View file

@ -101,6 +101,7 @@ namespace Config::Lua::Bindings::Internal {
{"no_screen_share", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_SCREEN_SHARE},
{"no_vrr", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_VRR},
{"stay_focused", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_STAY_FOCUSED},
{"confine_pointer", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_CONFINE_POINTER},
};
std::string argStr(lua_State* L, int idx);
@ -180,6 +181,9 @@ namespace Config::Lua::Bindings::Internal {
void setFn(lua_State* L, const char* name, lua_CFunction fn);
void setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn);
void markDispatcherTable(lua_State* L);
int wrapDispatcher(lua_State* L);
bool pushDispatcherFunction(lua_State* L, int idx);
template <typename T>
SParseError parseTableField(lua_State* L, int tableIdx, const char* field, T& parser) {
@ -196,6 +200,7 @@ namespace Config::Lua::Bindings::Internal {
return err;
}
bool hasTableField(lua_State* L, int tableIdx, const char* field);
void registerToplevelBindings(lua_State* L, CConfigManager* mgr);
void registerQueryBindings(lua_State* L);
void registerNotificationBindings(lua_State* L);

View file

@ -9,6 +9,7 @@
#include "../../../devices/IKeyboard.hpp"
#include "../../../managers/eventLoop/EventLoopManager.hpp"
#include <hyprutils/string/Numeric.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
@ -46,12 +47,12 @@ static bool isSymSpecial(std::string_view sv) {
}
static std::expected<void, std::string> parseKeyString(SKeybind& kb, std::string_view sv) {
bool modsEnded = false, specialSym = false;
CVarList2 vl(sv, 0, '+', true);
bool modsEnded = false, specialSym = false;
CVarList2 vl(sv, 0, '+', true);
uint32_t modMask = 0;
std::vector<xkb_keysym_t> keysyms;
std::string lastKeyArg;
uint32_t modMask = 0;
std::vector<std::pair<xkb_keysym_t, xkb_keycode_t>> keysyms;
std::string lastKeyArg;
if (sv == "catchall") {
kb.catchAll = true;
@ -86,6 +87,16 @@ static std::expected<void, std::string> parseKeyString(SKeybind& kb, std::string
continue;
}
if (arg.starts_with("code:") && isNumber(std::string{arg.substr(5)})) {
auto res = strToNumber<uint32_t>(arg.substr(5));
if (!res)
return std::unexpected(std::format("Invalid keycode: \"{}\".", arg));
keysyms.emplace_back(XKB_KEY_NoSymbol, xkb_keycode_t{*res});
continue;
}
auto sym = xkb_keysym_from_name(std::string{arg}.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);
if (sym == XKB_KEY_NoSymbol) {
@ -99,7 +110,7 @@ static std::expected<void, std::string> parseKeyString(SKeybind& kb, std::string
}
lastKeyArg = arg;
keysyms.emplace_back(sym);
keysyms.emplace_back(sym, 0);
}
kb.modmask = modMask;
@ -121,13 +132,12 @@ static int hlBind(lua_State* L) {
if (auto res = parseKeyString(kb, keys); !res)
return Internal::configError(L, std::format("hl.bind: failed to parse key string: {}", res.error()));
if (!lua_isfunction(L, 2))
if (!Internal::pushDispatcherFunction(L, 2))
return Internal::configError(L, "hl.bind: dispatcher must be a dispatcher (e.g. hl.dsp.window.close()) or a lua function");
if (kb.catchAll && mgr->m_currentSubmap.empty())
return Internal::configError(L, "hl.bind: catchall keybinds are only allowed in submaps.");
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
kb.handler = "__lua";
kb.arg = std::to_string(ref);
@ -166,6 +176,7 @@ static int hlBind(lua_State* L) {
kb.locked = getBool("locked");
kb.release = getBool("release");
kb.nonConsuming = getBool("non_consuming");
kb.autoConsuming = getBool("auto_consuming");
kb.transparent = getBool("transparent");
kb.ignoreMods = getBool("ignore_mods");
kb.dontInhibit = getBool("dont_inhibit");
@ -281,10 +292,9 @@ static int hlExecCmd(lua_State* L) {
}
static int hlDispatch(lua_State* L) {
if (!lua_isfunction(L, 1))
return Internal::configError(L, "hl.dispatch: expected a dispatcher function (e.g. hl.dsp.window.close())");
if (!Internal::pushDispatcherFunction(L, 1))
return Internal::configError(L, "hl.dispatch: expected a dispatcher (e.g. hl.dsp.window.close())");
lua_pushvalue(L, 1);
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(L); mgr)
status = mgr->guardedPCall(0, 1, 0, CConfigManager::LUA_TIMEOUT_DISPATCH_MS, "hl.dispatch");

View file

@ -0,0 +1,83 @@
#include "LuaGroup.hpp"
#include "LuaWindow.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/view/Group.hpp"
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Group";
static int groupEq(lua_State* L) {
const auto* lhs = sc<WP<Desktop::View::CGroup>*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<WP<Desktop::View::CGroup>*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int groupToString(lua_State* L) {
const auto* ref = sc<WP<Desktop::View::CGroup>*>(luaL_checkudata(L, 1, MT));
const auto group = ref->lock();
if (!group)
lua_pushstring(L, "HL.Group(expired)");
else
lua_pushfstring(L, "HL.Group(%p)", group.get());
return 1;
}
static int groupIndex(lua_State* L) {
auto* ref = sc<WP<Desktop::View::CGroup>*>(luaL_checkudata(L, 1, MT));
const auto group = ref->lock();
if (!group) {
Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object");
lua_pushnil(L);
return 1;
}
const std::string_view key = luaL_checkstring(L, 2);
if (key == "locked")
lua_pushboolean(L, group->locked());
else if (key == "denied")
lua_pushboolean(L, group->denied());
else if (key == "size")
lua_pushinteger(L, sc<lua_Integer>(group->size()));
else if (key == "current_index")
lua_pushinteger(L, sc<lua_Integer>(group->getCurrentIdx()) + 1);
else if (key == "current") {
const auto current = group->current();
if (current)
Objects::CLuaWindow::push(L, current);
else
lua_pushnil(L);
} else if (key == "members") {
lua_newtable(L);
int i = 1;
for (const auto& grouped : group->windows()) {
const auto groupedWindow = grouped.lock();
if (!groupedWindow)
continue;
Objects::CLuaWindow::push(L, groupedWindow);
lua_rawseti(L, -2, i++);
}
} else
lua_pushnil(L);
return 1;
}
void Objects::CLuaGroup::setup(lua_State* L) {
registerMetatable(L, MT, groupIndex, gcRef<WP<Desktop::View::CGroup>>, groupEq, groupToString);
}
void Objects::CLuaGroup::push(lua_State* L, SP<Desktop::View::CGroup> group) {
new (lua_newuserdata(L, sizeof(WP<Desktop::View::CGroup>))) WP<Desktop::View::CGroup>(group);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <hyprutils/memory/WeakPtr.hpp>
#include <lua.hpp>
#include "../../../desktop/view/Group.hpp"
namespace Config::Lua::Objects {
class CLuaGroup {
public:
static void setup(lua_State* L);
static void push(lua_State* L, SP<Desktop::View::CGroup> group);
};
};

View file

@ -143,6 +143,8 @@ static int keybindIndex(lua_State* L) {
lua_pushboolean(L, (*keybind)->release);
else if (key == "non_consuming")
lua_pushboolean(L, (*keybind)->nonConsuming);
else if (key == "auto_consuming")
lua_pushboolean(L, (*keybind)->autoConsuming);
else if (key == "transparent")
lua_pushboolean(L, (*keybind)->transparent);
else if (key == "ignore_mods")

View file

@ -1,6 +1,7 @@
#include "LuaWindow.hpp"
#include "LuaWorkspace.hpp"
#include "LuaMonitor.hpp"
#include "LuaGroup.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/view/Window.hpp"
@ -71,6 +72,10 @@ static int windowIndex(lua_State* L) {
lua_pushboolean(L, w->m_isMapped);
else if (key == "hidden")
lua_pushboolean(L, w->isHidden());
else if (key == "visible")
lua_pushboolean(L, w->visible());
else if (key == "accepts_input")
lua_pushboolean(L, w->acceptsInput());
else if (key == "at") {
lua_newtable(L);
lua_pushinteger(L, sc<int>(w->m_realPosition->goal().x));
@ -122,38 +127,7 @@ static int windowIndex(lua_State* L) {
return 1;
}
lua_newtable(L);
lua_pushboolean(L, w->m_group->locked());
lua_setfield(L, -2, "locked");
lua_pushboolean(L, w->m_group->denied());
lua_setfield(L, -2, "denied");
lua_pushinteger(L, sc<lua_Integer>(w->m_group->size()));
lua_setfield(L, -2, "size");
lua_pushinteger(L, sc<lua_Integer>(w->m_group->getCurrentIdx()) + 1);
lua_setfield(L, -2, "current_index");
const auto current = w->m_group->current();
if (current)
Objects::CLuaWindow::push(L, current);
else
lua_pushnil(L);
lua_setfield(L, -2, "current");
lua_newtable(L);
int i = 1;
for (const auto& grouped : w->m_group->windows()) {
const auto groupedWindow = grouped.lock();
if (!groupedWindow)
continue;
Objects::CLuaWindow::push(L, groupedWindow);
lua_rawseti(L, -2, i++);
}
lua_setfield(L, -2, "members");
Objects::CLuaGroup::push(L, w->m_group);
} else if (key == "tags") {
lua_newtable(L);

View file

@ -1,10 +1,19 @@
#include "LuaWorkspace.hpp"
#include "LuaMonitor.hpp"
#include "LuaWindow.hpp"
#include "LuaGroup.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/Workspace.hpp"
#include "../../../desktop/view/Group.hpp"
#include "../../../helpers/Monitor.hpp"
#include "../../../layout/space/Space.hpp"
#include "../../../layout/algorithm/Algorithm.hpp"
#include "../../../layout/algorithm/TiledAlgorithm.hpp"
#include "../../../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../../../Compositor.hpp"
#include <algorithm>
#include <string_view>
using namespace Config::Lua;
@ -32,6 +41,55 @@ static int workspaceToString(lua_State* L) {
return 1;
}
static int workspaceGetWindows(lua_State* L) {
auto* ref = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto ws = ref->lock();
if (!ws || ws->inert()) {
lua_newtable(L);
return 1;
}
lua_newtable(L);
int idx = 1;
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == ws) {
Objects::CLuaWindow::push(L, w);
lua_rawseti(L, -2, idx++);
}
}
return 1;
}
static int workspaceGetGroups(lua_State* L) {
auto* ref = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto ws = ref->lock();
if (!ws || ws->inert()) {
lua_newtable(L);
return 1;
}
lua_newtable(L);
int idx = 1;
std::vector<Desktop::View::CGroup*> pushedGroups;
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace != ws || !w->m_group)
continue;
if (std::ranges::find(pushedGroups, w->m_group.get()) != pushedGroups.end())
continue;
pushedGroups.push_back(w->m_group.get());
Objects::CLuaGroup::push(L, w->m_group);
lua_rawseti(L, -2, idx++);
}
return 1;
}
static int workspaceIndex(lua_State* L) {
auto* ref = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto ws = ref->lock();
@ -70,6 +128,35 @@ static int workspaceIndex(lua_State* L) {
lua_pushboolean(L, ws->m_hasFullscreenWindow);
else if (key == "is_persistent")
lua_pushboolean(L, ws->isPersistent());
else if (key == "is_empty")
lua_pushboolean(L, ws->getWindows() == 0);
else if (key == "config_name")
lua_pushstring(L, ws->getConfigName().c_str());
else if (key == "tiled_layout") {
std::string layoutName = "unknown";
if (ws->m_space && ws->m_space->algorithm() && ws->m_space->algorithm()->tiledAlgo()) {
const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo();
layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get()));
}
lua_pushstring(L, layoutName.c_str());
} else if (key == "last_window") {
const auto lastWindow = ws->m_lastFocusedWindow.lock();
if (lastWindow)
Objects::CLuaWindow::push(L, lastWindow);
else
lua_pushnil(L);
} else if (key == "fullscreen_window") {
const auto fsWindow = ws->getFullscreenWindow();
if (fsWindow)
Objects::CLuaWindow::push(L, fsWindow);
else
lua_pushnil(L);
} else if (key == "get_windows")
lua_pushcfunction(L, workspaceGetWindows);
else if (key == "get_groups")
lua_pushcfunction(L, workspaceGetGroups);
else if (key == "groups")
lua_pushinteger(L, sc<lua_Integer>(ws->getGroups()));
else
lua_pushnil(L);

View file

@ -29,7 +29,6 @@ namespace Config {
SConfigError() = default;
SConfigError(std::string msg, eConfigErrorLevel lvl = eConfigErrorLevel::ERROR, eConfigErrorCode c = eConfigErrorCode::UNKNOWN) :
message(std::move(msg)), level(lvl), code(c) {}
SConfigError(const char* msg) : message(msg ? msg : "") {}
std::string message;
eConfigErrorLevel level = eConfigErrorLevel::ERROR;

View file

@ -94,7 +94,7 @@ static bool tryMoveFocusToMonitor(PHLMONITOR monitor) {
const auto PNEWMAINWORKSPACE = monitor->m_activeWorkspace;
const auto PNEWWORKSPACE = monitor->m_activeSpecialWorkspace ? monitor->m_activeSpecialWorkspace : PNEWMAINWORKSPACE;
const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow();
auto PNEWWINDOW = PNEWWORKSPACE->getFocusCandidate();
if (PNEWWINDOW) {
updateRelativeCursorCoords();
@ -230,10 +230,12 @@ ActionResult Actions::pinWindow(eTogglableAction action, std::optional<PHLWINDOW
return {};
window->m_pinned = wantPin;
window->updateFullscreenInputState();
*window->alpha(Desktop::View::WINDOW_ALPHA_FULLSCREEN) = window->isBlockedByFullscreen() ? 0.F : 1.F;
const auto PMONITOR = window->m_monitor.lock();
if (!PMONITOR)
return std::unexpected("Window has no monitor");
return actionError("Window has no monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
window->layoutTarget()->assignToSpace(PMONITOR->m_activeWorkspace->m_space);
window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED);
@ -289,7 +291,7 @@ ActionResult Actions::moveToWorkspace(PHLWORKSPACE ws, bool silent, std::optiona
return {};
if (!ws)
return std::unexpected("Invalid workspace");
return actionError("No workspace to move to", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_ARGUMENT);
if (ws->m_id == window->workspaceID())
return {};
@ -349,7 +351,7 @@ ActionResult Actions::moveFocus(Math::eDirection dir) {
}
const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ?
g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) :
g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT, true) :
g_pCompositor->getWindowInDirection(PLASTWINDOW, dir);
if (*PGROUPCYCLE && PLASTWINDOW->m_group) {
@ -377,7 +379,7 @@ ActionResult Actions::moveFocus(Math::eDirection dir) {
const auto PMONITOR = PLASTWINDOW->m_monitor.lock();
if (!PMONITOR)
return std::unexpected("Window has no monitor");
return actionError("Window has no monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) {
if (STICKS(PLASTWINDOW->m_position.x, PMONITOR->m_position.x) && STICKS(PLASTWINDOW->m_size.x, PMONITOR->m_size.x))
@ -420,7 +422,7 @@ ActionResult Actions::focus(PHLWINDOW window) {
const auto PWORKSPACE = window->m_workspace;
if (!PWORKSPACE)
return std::unexpected("Window has no workspace");
return actionError("Window has no workspace", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
updateRelativeCursorCoords();
@ -538,7 +540,7 @@ ActionResult Actions::moveCursorToCorner(int corner, std::optional<PHLWINDOW> w)
return {};
if (corner < 0 || corner > 3)
return std::unexpected("Corner must be 0-3");
return actionError("Corner must be 0 - 3", eActionErrorLevel::ERROR, eActionErrorCode::INVALID_ARGUMENT);
switch (corner) {
case 0: g_pCompositor->warpCursorTo({window->m_realPosition->value().x, window->m_realPosition->value().y + window->m_realSize->value().y}, true); break;
@ -563,7 +565,7 @@ ActionResult Actions::resize(const Vector2D& size, bool relative, std::optional<
return actionError("Window is fullscreen", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
if (!relative && (size.x < 1 || size.y < 1))
return std::unexpected("Invalid size");
return actionError("Invalid size", eActionErrorLevel::ERROR, eActionErrorCode::INVALID_ARGUMENT);
const auto delta = relative ? size : size - window->m_realSize->goal();
@ -603,6 +605,19 @@ ActionResult Actions::tag(const std::string& tagStr, std::optional<PHLWINDOW> w)
return {};
}
ActionResult Actions::clearTags(std::optional<PHLWINDOW> w) {
auto window = xtract(w);
if (!window)
return {};
if (window->m_ruleApplicator->m_tagKeeper.clearTags()) {
window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TAG);
window->updateDecorationValues();
}
return {};
}
ActionResult Actions::swapNext(const bool next, std::optional<PHLWINDOW> w) {
auto window = xtract(w);
if (!window)
@ -812,7 +827,7 @@ ActionResult Actions::setProp(const std::string& PROP, const std::string& VAL, s
else if (PROP == "animation")
parsePropTrivial(PWINDOW->m_ruleApplicator->animationStyle(), VAL);
else
return std::unexpected("prop not found");
return actionError("Invalid prop name", eActionErrorLevel::ERROR, eActionErrorCode::INVALID_ARGUMENT);
} catch (std::exception& e) { return std::unexpected(std::format("Error parsing prop value: {}", std::string(e.what()))); }
@ -884,14 +899,14 @@ ActionResult Actions::setGroupActive(int index, std::optional<PHLWINDOW> w) {
ActionResult Actions::changeWorkspace(PHLWORKSPACE ws) {
if (!ws)
return std::unexpected("Invalid workspace");
return actionError("Invalid workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Config::INTEGER>("binds:hide_special_on_workspace_change");
static auto PWORKSPACECENTERON = CConfigValue<Config::INTEGER>("binds:workspace_center_on");
const auto PMONITOR = Desktop::focusState()->monitor();
if (!PMONITOR)
return std::unexpected("No monitor");
return actionError("No focused monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
if (ws->m_isSpecialWorkspace) {
PMONITOR->setSpecialWorkspace(ws);
@ -905,7 +920,7 @@ ActionResult Actions::changeWorkspace(PHLWORKSPACE ws) {
const auto PMONITORWORKSPACEOWNER = PMONITOR == ws->m_monitor ? PMONITOR : ws->m_monitor.lock();
if (!PMONITORWORKSPACEOWNER)
return std::unexpected("Workspace has no monitor");
return actionError("Workspace has no monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
updateRelativeCursorCoords();
@ -916,11 +931,13 @@ ActionResult Actions::changeWorkspace(PHLWORKSPACE ws) {
PMONITORWORKSPACEOWNER->changeWorkspace(ws, false, true);
if (PMONITOR != PMONITORWORKSPACEOWNER) {
Vector2D middle = PMONITORWORKSPACEOWNER->middle();
if (const auto PLAST = ws->getLastFocusedWindow(); PLAST) {
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
Vector2D middle = PMONITORWORKSPACEOWNER->middle();
auto pWindow = ws->getFocusCandidate();
if (pWindow) {
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
if (*PWORKSPACECENTERON == 1)
middle = PLAST->middle();
middle = pWindow->middle();
}
g_pCompositor->warpCursorTo(middle);
}
@ -995,13 +1012,13 @@ static PHLWORKSPACE resolveWorkspaceForChange(const std::string& args) {
ActionResult Actions::changeWorkspace(const std::string& ws) {
auto p = resolveWorkspaceForChange(ws);
if (!p)
return std::unexpected("invalid workspace");
return actionError("Bad workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
return Actions::changeWorkspace(p);
}
ActionResult Actions::renameWorkspace(PHLWORKSPACE ws, const std::string& s) {
if (!ws)
return std::unexpected("Invalid workspace");
return actionError("Bad workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
ws->rename(s);
@ -1010,9 +1027,9 @@ ActionResult Actions::renameWorkspace(PHLWORKSPACE ws, const std::string& s) {
ActionResult Actions::moveToMonitor(PHLWORKSPACE ws, PHLMONITOR mon) {
if (!ws)
return std::unexpected("Invalid workspace");
return actionError("Bad workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
if (!mon)
return std::unexpected("Invalid monitor");
return actionError("Bad monitor", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
g_pCompositor->moveWorkspaceToMonitor(ws, mon);
@ -1021,16 +1038,16 @@ ActionResult Actions::moveToMonitor(PHLWORKSPACE ws, PHLMONITOR mon) {
ActionResult Actions::changeWorkspaceOnCurrentMonitor(PHLWORKSPACE ws) {
if (!ws)
return std::unexpected("Invalid workspace");
return actionError("Bad workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
const auto PCURRMONITOR = Desktop::focusState()->monitor();
if (!PCURRMONITOR)
return std::unexpected("No current monitor");
return actionError("No focused monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
if (ws->m_monitor != PCURRMONITOR) {
const auto POLDMONITOR = ws->m_monitor.lock();
if (!POLDMONITOR)
return std::unexpected("Workspace has no monitor");
return actionError("Workspace has no monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
if (POLDMONITOR->activeWorkspaceID() == ws->m_id) {
g_pCompositor->swapActiveWorkspaces(POLDMONITOR, PCURRMONITOR);
@ -1045,11 +1062,11 @@ ActionResult Actions::changeWorkspaceOnCurrentMonitor(PHLWORKSPACE ws) {
ActionResult Actions::toggleSpecial(PHLWORKSPACE special) {
if (!special || !special->m_isSpecialWorkspace)
return std::unexpected("Invalid special workspace");
return actionError("Bad special workspace", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
const auto PMONITOR = Desktop::focusState()->monitor();
if (!PMONITOR)
return std::unexpected("No monitor");
return actionError("No focused monitor", eActionErrorLevel::WARNING, eActionErrorCode::INVALID_STATE);
bool requestedWorkspaceIsAlreadyOpen = false;
auto specialOpenOnMonitor = PMONITOR->activeSpecialWorkspaceID();
@ -1088,7 +1105,7 @@ ActionResult Actions::toggleSpecial(PHLWORKSPACE special) {
ActionResult Actions::focusMonitor(PHLMONITOR mon) {
if (!mon)
return std::unexpected("Invalid monitor");
return actionError("Bad monitor", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
tryMoveFocusToMonitor(mon);
@ -1097,7 +1114,7 @@ ActionResult Actions::focusMonitor(PHLMONITOR mon) {
ActionResult Actions::swapActiveWorkspaces(PHLMONITOR mon1, PHLMONITOR mon2) {
if (!mon1 || !mon2)
return std::unexpected("Invalid monitor");
return actionError("Bad monitor", eActionErrorLevel::WARNING, eActionErrorCode::NO_TARGET);
if (mon1 == mon2)
return {};
@ -1406,7 +1423,7 @@ ActionResult Actions::pass(std::optional<PHLWINDOW> w) {
return {};
if (!g_pSeatManager->m_keyboard)
return std::unexpected("No keyboard");
return actionError("No keyboard connected", eActionErrorLevel::INFO, eActionErrorCode::NO_TARGET);
const auto& S = *Config::Actions::state();
const auto XWTOXW = window->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11;
@ -1474,7 +1491,7 @@ ActionResult Actions::pass(uint32_t modMask, uint32_t key, std::optional<PHLWIND
if (window) {
if (!g_pSeatManager->m_keyboard)
return std::unexpected("No keyboard");
return actionError("No keyboard connected", eActionErrorLevel::INFO, eActionErrorCode::NO_TARGET);
if (!isMouse)
g_pSeatManager->setKeyboardFocus(window->wlSurface()->resource());
@ -1651,7 +1668,7 @@ ActionResult Actions::cycleNext(const bool next, std::optional<bool> onlyTiled,
if (onlyFloating.value_or(false))
floatStatus = true;
const auto& cycled = g_pCompositor->getWindowCycle(window, true, floatStatus, false, !next);
const auto& cycled = g_pCompositor->getWindowCycle(window, true, floatStatus, false, !next, window->m_workspace && window->m_workspace->m_hasFullscreenWindow);
switchToWindow(cycled);

View file

@ -53,6 +53,7 @@ namespace Config::Actions {
ActionResult move(const Vector2D& pos, bool relative = false, std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult cycleNext(const bool next, std::optional<bool> onlyTiled, std::optional<bool> onlyFloating, std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult tag(const std::string& tag, std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult clearTags(std::optional<PHLWINDOW> w = std::nullopt);
ActionResult pass(std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult pass(uint32_t modMask, uint32_t key, std::optional<PHLWINDOW> window = std::nullopt /* Active */);
ActionResult sendKeyState(uint32_t modMask, uint32_t key, uint32_t state, std::optional<PHLWINDOW> window = std::nullopt /* Active */);

View file

@ -3,7 +3,7 @@
using namespace Config;
void CWorkspaceRule::mergeLeft(const CWorkspaceRule& other) {
if (m_monitor.empty())
if (!other.m_monitor.empty())
m_monitor = other.m_monitor;
if (m_workspaceString.empty())
m_workspaceString = other.m_workspaceString;
@ -12,10 +12,10 @@ void CWorkspaceRule::mergeLeft(const CWorkspaceRule& other) {
if (m_workspaceId == WORKSPACE_INVALID)
m_workspaceId = other.m_workspaceId;
if (other.m_isDefault)
m_isDefault = true;
if (other.m_isPersistent)
m_isPersistent = true;
if (other.m_isDefault.has_value())
m_isDefault = other.m_isDefault;
if (other.m_isPersistent.has_value())
m_isPersistent = other.m_isPersistent;
if (other.m_gapsIn.has_value())
m_gapsIn = other.m_gapsIn;
if (other.m_gapsOut.has_value())

View file

@ -27,8 +27,8 @@ namespace Config {
std::string m_workspaceString = "";
std::string m_workspaceName = "";
WORKSPACEID m_workspaceId = -1;
bool m_isDefault = false;
bool m_isPersistent = false;
std::optional<bool> m_isDefault;
std::optional<bool> m_isPersistent;
std::optional<CCssGapData> m_gapsIn;
std::optional<CCssGapData> m_gapsOut;
std::optional<CCssGapData> m_floatGaps = m_gapsOut;

View file

@ -55,7 +55,7 @@ std::string CWorkspaceRuleManager::getDefaultWorkspaceFor(const std::string& nam
if (!other->m_enabled)
continue;
if (other->m_isDefault) {
if (other->m_isDefault.value_or(false)) {
if (other->m_monitor == name)
return other->m_workspaceString;
if (other->m_monitor.starts_with("desc:")) {

View file

@ -412,6 +412,7 @@ std::vector<SP<IValue>> Values::getConfigValues() {
MS<Int>("group:groupbar:priority", "sets the decoration priority for groupbars", 3, {.min = 0, .max = 6}),
MS<Bool>("group:groupbar:render_titles", "whether to render titles in the group bar decoration", true),
MS<Bool>("group:groupbar:scrolling", "whether scrolling in the groupbar changes group active window", true),
MS<Bool>("group:groupbar:middle_click_close", "whether middle clicking the groupbar closes the clicked window", true),
MS<Int>("group:groupbar:rounding", "how much to round the groupbar", 1, {.min = 0, .max = 20}),
MS<Float>("group:groupbar:rounding_power", "rounding power of groupbar corners (2 is a circle)", 2, {.min = 2, .max = 10}),
MS<Int>("group:groupbar:gradient_rounding", "how much to round the groupbar gradient", 2, {.min = 0, .max = 20}),

View file

@ -49,9 +49,10 @@ using namespace Hyprutils::OS;
#include "../devices/ITouch.hpp"
#include "../devices/Tablet.hpp"
#include "../protocols/GlobalShortcuts.hpp"
#include "debug/log/RollingLogFollow.hpp"
#include "config/ConfigManager.hpp"
#include "helpers/MiscFunctions.hpp"
#include "../debug/log/RollingLogFollow.hpp"
#include "../config/ConfigManager.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/SystemInfo.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/view/Group.hpp"
#include "../desktop/rule/Engine.hpp"
@ -382,6 +383,8 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"address": "0x{:x}",
"mapped": {},
"hidden": {},
"visible": {},
"acceptsInput": {},
"at": [{}, {}],
"size": [{}, {}],
"workspace": {{
@ -410,28 +413,31 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"contentType": "{}",
"stableId": "{:x}"
}},)#",
rc<uintptr_t>(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc<int>(w->m_realPosition->goal().x),
sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class),
escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? "true" : "false"),
(w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
(g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")),
escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID);
rc<uintptr_t>(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), (w->visible() ? "true" : "false"),
(w->acceptsInput() ? "true" : "false"), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x),
sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name),
(sc<int>(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass),
escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"),
sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"), getGroupedData(w, format),
getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), (g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"),
escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")), escapeJSONStrings(NContentType::toString(w->getContentType())),
w->m_stableID);
} else {
return std::format(
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tvisible: {}\n\tacceptsInput: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: "
"{}\n\tclass: {}\n\ttitle: "
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
"{}\n\txwayland: {}\n\tpinned: "
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: "
"{}\n\txdgTag: "
"{}\n\txdgDescription: {}\n\tcontentType: {}\n\tstableID: {:x}\n\n",
rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y),
sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
(!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(),
sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), sc<int>(w->m_createdOverFullscreen),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)),
w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID);
rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->visible()), sc<int>(w->acceptsInput()),
sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y),
w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, (!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), w->monitorID(), w->m_class,
w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(), sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal),
sc<uint8_t>(w->m_fullscreenState.client), sc<int>(w->m_createdOverFullscreen), getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()),
getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)), w->xdgTag().value_or(""), w->xdgDescription().value_or(""),
NContentType::toString(w->getContentType()), w->m_stableID);
}
}
@ -502,8 +508,8 @@ static std::string getWorkspaceRuleData(const Config::CWorkspaceRule& r, eHyprCt
const auto boolToString = [](const bool b) -> std::string { return b ? "true" : "false"; };
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
const std::string monitor = r.m_monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.m_monitor));
const std::string default_ = sc<bool>(r.m_isDefault) ? std::format(",\n \"default\": {}", boolToString(r.m_isDefault)) : "";
const std::string persistent = sc<bool>(r.m_isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.m_isPersistent)) : "";
const std::string default_ = sc<bool>(r.m_isDefault) ? std::format(",\n \"default\": {}", boolToString(r.m_isDefault.value())) : "";
const std::string persistent = sc<bool>(r.m_isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.m_isPersistent.value())) : "";
const std::string gapsIn = sc<bool>(r.m_gapsIn) ?
std::format(",\n \"gapsIn\": [{}, {}, {}, {}]", r.m_gapsIn.value().m_top, r.m_gapsIn.value().m_right, r.m_gapsIn.value().m_bottom, r.m_gapsIn.value().m_left) :
"";
@ -526,8 +532,8 @@ static std::string getWorkspaceRuleData(const Config::CWorkspaceRule& r, eHyprCt
return result;
} else {
const std::string monitor = std::format("\tmonitor: {}\n", r.m_monitor.empty() ? "<unset>" : escapeJSONStrings(r.m_monitor));
const std::string default_ = std::format("\tdefault: {}\n", sc<bool>(r.m_isDefault) ? boolToString(r.m_isDefault) : "<unset>");
const std::string persistent = std::format("\tpersistent: {}\n", sc<bool>(r.m_isPersistent) ? boolToString(r.m_isPersistent) : "<unset>");
const std::string default_ = std::format("\tdefault: {}\n", sc<bool>(r.m_isDefault) ? boolToString(r.m_isDefault.value()) : "<unset>");
const std::string persistent = std::format("\tpersistent: {}\n", sc<bool>(r.m_isPersistent) ? boolToString(r.m_isPersistent.value()) : "<unset>");
const std::string gapsIn = sc<bool>(r.m_gapsIn) ?
std::format("\tgapsIn: {} {} {} {}\n", std::to_string(r.m_gapsIn.value().m_top), std::to_string(r.m_gapsIn.value().m_right),
std::to_string(r.m_gapsIn.value().m_bottom), std::to_string(r.m_gapsIn.value().m_left)) :
@ -1010,6 +1016,8 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
ret += "e";
if (kb->nonConsuming)
ret += "n";
if (kb->autoConsuming)
ret += "a";
if (kb->hasDescription)
ret += "d";
@ -1029,6 +1037,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
"repeat": {},
"longPress": {},
"non_consuming": {},
"auto_consuming": {},
"has_description": {},
"modmask": {},
"submap": "{}",
@ -1041,8 +1050,8 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
"arg": "{}"
}},)#",
kb->locked ? "true" : "false", kb->mouse ? "true" : "false", kb->release ? "true" : "false", kb->repeat ? "true" : "false", kb->longPress ? "true" : "false",
kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal,
escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler),
kb->nonConsuming ? "true" : "false", kb->autoConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name),
kb->submapUniversal, escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler),
escapeJSONStrings(kb->arg));
}
trimTrailingComma(ret);
@ -1053,193 +1062,19 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
}
std::string versionRequest(eHyprCtlOutputFormat format, std::string request) {
return Helpers::SystemInfo::getVersion(format);
}
auto commitMsg = trim(GIT_COMMIT_MESSAGE);
std::ranges::replace(commitMsg, '#', ' ');
if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {
std::string result = std::format("Hyprland {} built from branch {} at commit {} {} ({}).\n"
"Date: {}\n"
"Tag: {}, commits: {}\n",
HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS);
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "Version ABI string: ";
result += __hyprland_api_get_hash();
result += "\n";
#if (!ISDEBUG && !defined(NO_XWAYLAND) && !defined(BUILT_WITH_NIX))
result += "no flags were set\n";
#else
result += "flags set:\n";
#if ISDEBUG
result += "debug\n";
#endif
#ifdef NO_XWAYLAND
result += "no xwayland\n";
#endif
#ifdef BUILT_WITH_NIX
result += "nix\n";
#endif
#endif
return result;
} else {
std::string result = std::format(
R"#({{
"branch": "{}",
"commit": "{}",
"version": "{}",
"dirty": {},
"commit_message": "{}",
"commit_date": "{}",
"tag": "{}",
"commits": "{}",
"buildAquamarine": "{}",
"buildHyprlang": "{}",
"buildHyprutils": "{}",
"buildHyprcursor": "{}",
"buildHyprgraphics": "{}",
"systemAquamarine": "{}",
"systemHyprlang": "{}",
"systemHyprutils": "{}",
"systemHyprcursor": "{}",
"systemHyprgraphics": "{}",
"abiHash": "{}",
"flags": [)#",
GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG,
GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION, getSystemLibraryVersion("aquamarine"),
getSystemLibraryVersion("hyprlang"), getSystemLibraryVersion("hyprutils"), getSystemLibraryVersion("hyprcursor"), getSystemLibraryVersion("hyprgraphics"),
__hyprland_api_get_hash());
#if ISDEBUG
result += "\"debug\",";
#endif
#ifdef NO_XWAYLAND
result += "\"no xwayland\",";
#endif
#ifdef BUILT_WITH_NIX
result += "\"nix\",";
#endif
trimTrailingComma(result);
result += "]\n}";
return result;
}
return ""; // make the compiler happy
static std::string statusRequest(eHyprCtlOutputFormat format, std::string request) {
return Helpers::SystemInfo::getStatus(format);
}
std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request) {
std::string result = versionRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, "");
static auto check = [](bool y) -> std::string { return y ? "✔️" : ""; };
static auto backend = [](Aquamarine::eBackendType t) -> std::string {
switch (t) {
case Aquamarine::AQ_BACKEND_DRM: return "drm";
case Aquamarine::AQ_BACKEND_HEADLESS: return "headless";
case Aquamarine::AQ_BACKEND_WAYLAND: return "wayland";
default: break;
}
return "?";
};
result += "\n\nSystem Information:\n";
struct utsname unameInfo;
uname(&unameInfo);
result += "System name: " + std::string{unameInfo.sysname} + "\n";
result += "Node name: " + std::string{unameInfo.nodename} + "\n";
result += "Release: " + std::string{unameInfo.release} + "\n";
result += "Version: " + std::string{unameInfo.version} + "\n";
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "\n\n";
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | grep -F -A4 vga");
#elif defined(__arm__) || defined(__aarch64__)
std::string GPUINFO;
const std::filesystem::path dev_tree = "/proc/device-tree";
try {
if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {
std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {
if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with("soc")) {
std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {
if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with("gpu")) {
std::filesystem::path file_path = sub_entry.path() / "compatible";
std::ifstream file(file_path);
if (file)
GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
});
}
});
}
} catch (...) { GPUINFO = "error"; }
#else
const std::string GPUINFO = execAndGet("lspci -vnn | grep -E '(VGA|Display|3D)'");
#endif
result += "GPU information: \n" + GPUINFO;
if (GPUINFO.contains("NVIDIA") && std::filesystem::exists("/proc/driver/nvidia/version")) {
std::ifstream file("/proc/driver/nvidia/version");
std::string line;
if (file.is_open()) {
while (std::getline(file, line)) {
if (!line.contains("NVRM"))
continue;
result += line;
result += "\n";
}
} else
result += "error";
}
result += "\n\n";
if (std::ifstream file("/etc/os-release"); file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
result += "os-release: " + buffer.str() + "\n\n";
} else
result += "os-release: error\n\n";
result += "plugins:\n";
if (g_pPluginSystem) {
for (auto const& pl : g_pPluginSystem->getAllPlugins()) {
result += std::format(" {} by {} ver {}\n", pl->m_name, pl->m_author, pl->m_version);
}
} else
result += "\tunknown: not runtime\n";
if (g_pHyprOpenGL) {
result += std::format("\nExplicit sync: {}", g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext ? "supported" : "missing");
result += std::format("\nGL ver: {}", g_pHyprOpenGL->m_eglContextVersion == CHyprOpenGLImpl::EGL_CONTEXT_GLES_3_2 ? "3.2" : "3.0");
}
if (g_pCompositor) {
result += std::format("\nBackend: {}", g_pCompositor->m_aqBackend->hasSession() ? "drm" : "sessionless");
result += "\n\nMonitor info:";
for (const auto& m : g_pCompositor->m_monitors) {
result += std::format("\n\tPanel {}: {}x{}, {} {} {} {} -> backend {}\n\t\texplicit {}\n\t\tedid:\n\t\t\thdr {}\n\t\t\tchroma {}\n\t\t\tbt2020 {}\n\t\tvrr capable "
"{}\n\t\tnon-desktop {}\n\t\t",
m->m_name, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_output->name, m->m_output->make, m->m_output->model, m->m_output->serial,
backend(m->m_output->getBackend()->type()), check(m->m_output->supportsExplicit), check(m->m_output->parsedEDID.hdrMetadata.has_value()),
check(m->m_output->parsedEDID.chromaticityCoords.has_value()), check(m->m_output->parsedEDID.supportsBT2020), check(m->m_output->vrrCapable),
check(m->m_output->nonDesktop));
}
}
auto result = Helpers::SystemInfo::getSystemInfo();
if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.sysInfoConfig) {
result += "\n======Config-Start======\n";
result += "\n\n======Config-Start======\n";
result += Config::mgr()->getConfigString();
result += "\n======Config-End========\n";
}
@ -1797,6 +1632,8 @@ static std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string re
if (format == FORMAT_NORMAL) {
if (TYPE == typeid(Config::INTEGER))
return std::format("int: {}\nset: {}", **rc<Config::INTEGER* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(Config::BOOL))
return std::format("bool: {}\nset: {}", **rc<Config::BOOL* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(Config::FLOAT))
return std::format("float: {:2f}\nset: {}", **rc<Config::FLOAT* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(Config::VEC2))
@ -1809,9 +1646,19 @@ static std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string re
return std::format("str: {}\nset: {}", **rc<Config::STRING* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(void*))
return std::format("custom type: {}\nset: {}", rc<Config::IComplexConfigValue*>((*rc<Hyprlang::CUSTOMTYPE* const*>(VAL))->getData())->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::IComplexConfigValue))
return std::format("custom type: {}\nset: {}", (*rc<Config::IComplexConfigValue* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CCssGapData))
return std::format("css gap data: {}\nset: {}", (*rc<Config::CCssGapData* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CGradientValueData))
return std::format("gradient data: {}\nset: {}", (*rc<Config::CGradientValueData* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CFontWeightConfigValueData))
return std::format("font weight data: {}\nset: {}", (*rc<Config::CFontWeightConfigValueData* const*>(VAL))->toString(), VAR.setByUser);
} else {
if (TYPE == typeid(Config::INTEGER))
return std::format(R"({{"option": "{}", "int": {}, "set": {} }})", curitem, **rc<Config::INTEGER* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(Config::BOOL))
return std::format(R"({{"option": "{}", "bool": {}, "set": {} }})", curitem, (**rc<Config::BOOL* const*>(VAL)) ? "true" : "false", VAR.setByUser);
else if (TYPE == typeid(Config::FLOAT))
return std::format(R"({{"option": "{}", "float": {:2f}, "set": {} }})", curitem, **rc<Config::FLOAT* const*>(VAL), VAR.setByUser);
else if (TYPE == typeid(Config::VEC2))
@ -1827,6 +1674,15 @@ static std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string re
else if (TYPE == typeid(void*))
return std::format(R"({{"option": "{}", "custom": "{}", "set": {} }})", curitem,
rc<Config::IComplexConfigValue*>((*rc<Hyprlang::CUSTOMTYPE* const*>(VAL))->getData())->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::IComplexConfigValue))
return std::format(R"({{"option": "{}", "custom": "{}", "set": {} }})", curitem, (*rc<Config::IComplexConfigValue* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CCssGapData))
return std::format(R"({{"option": "{}", "css": "{}", "set": {} }})", curitem, (*rc<Config::CCssGapData* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CGradientValueData))
return std::format(R"({{"option": "{}", "gradient": "{}", "set": {} }})", curitem, (*rc<Config::CGradientValueData* const*>(VAL))->toString(), VAR.setByUser);
else if (TYPE == typeid(Config::CFontWeightConfigValueData))
return std::format(R"({{"option": "{}", "font_weight": "{}", "set": {} }})", curitem, (*rc<Config::CFontWeightConfigValueData* const*>(VAL))->toString(),
VAR.setByUser);
}
return "invalid type (internal error)";
@ -2089,43 +1945,6 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques
return format == FORMAT_JSON ? std::format("\"{}\"\n", escapeJSONStrings(submap)) : (submap + "\n");
}
static std::string statusRequest(eHyprCtlOutputFormat format, std::string request) {
Aquamarine::eBackendType backendType = Aquamarine::eBackendType::AQ_BACKEND_NULL;
for (const auto& i : g_pCompositor->m_aqBackend->getImplementations()) {
if (i->type() == Aquamarine::eBackendType::AQ_BACKEND_NULL || i->type() == Aquamarine::eBackendType::AQ_BACKEND_HEADLESS)
continue;
backendType = i->type();
break;
}
std::string backendStr;
switch (backendType) {
case Aquamarine::AQ_BACKEND_DRM: backendStr = "drm"; break;
case Aquamarine::AQ_BACKEND_WAYLAND: backendStr = "wayland"; break;
default: backendStr = "error"; break;
}
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
return std::format(R"#(
{{
"configProvider": "{}",
"backend": "{}"
}}
)#",
Config::typeToString(Config::mgr()->type()), backendStr);
}
return std::format(R"#(
configProvider: {}
backend: {}
)#",
Config::typeToString(Config::mgr()->type()), backendStr);
}
static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {
CVarList vars(request, 0, ' ');

View file

@ -52,7 +52,7 @@ void CWorkspace::init(PHLWORKSPACE self) {
m_inert = false;
const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(self).value_or(Config::CWorkspaceRule{});
setPersistent(WORKSPACERULE.m_isPersistent);
setPersistent(WORKSPACERULE.m_isPersistent.value_or(false));
if (self->m_wasCreatedEmpty)
if (auto cmd = WORKSPACERULE.m_onCreatedEmptyRunCmd)
@ -83,6 +83,18 @@ PHLWINDOW CWorkspace::getLastFocusedWindow() {
return m_lastFocusedWindow.lock();
}
PHLWINDOW CWorkspace::getFocusCandidate() {
auto pWindow = getLastFocusedWindow();
if (!pWindow)
pWindow = getTopLeftWindow();
if (!pWindow)
pWindow = getFirstWindow();
return pWindow;
}
std::string CWorkspace::getConfigName() {
if (m_isSpecialWorkspace) {
return m_name;
@ -426,7 +438,7 @@ int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> on
continue;
if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value()))
continue;
if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value()))
if (onlyVisible.has_value() && (!t->window() || t->window()->targetVisible() != onlyVisible.value()))
continue;
no++;
}
@ -445,7 +457,7 @@ int CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onl
continue;
if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value())
continue;
if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value())
if (onlyVisible.has_value() && g->current()->targetVisible() != onlyVisible.value())
continue;
no++;
}
@ -454,7 +466,7 @@ int CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onl
PHLWINDOW CWorkspace::getFirstWindow() {
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == m_self && w->m_isMapped && !w->isHidden())
if (w->m_workspace == m_self && w->m_isMapped && w->acceptsInput())
return w;
}
@ -465,7 +477,7 @@ PHLWINDOW CWorkspace::getTopLeftWindow() {
const auto PMONITOR = m_monitor.lock();
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace != m_self || !w->m_isMapped || w->isHidden())
if (w->m_workspace != m_self || !w->m_isMapped || !w->acceptsInput())
continue;
const auto WINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved();
@ -517,9 +529,9 @@ void CWorkspace::rename(const std::string& name) {
m_name = name;
const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_self.lock()).value_or(Config::CWorkspaceRule{});
setPersistent(WORKSPACERULE.m_isPersistent);
setPersistent(WORKSPACERULE.m_isPersistent.value_or(false));
if (WORKSPACERULE.m_isPersistent)
if (WORKSPACERULE.m_isPersistent.value_or(false))
g_pCompositor->ensurePersistentWorkspacesPresent(std::vector<Config::CWorkspaceRule>{WORKSPACERULE}, m_self.lock());
g_pEventManager->postEvent({.event = "renameworkspace", .data = std::to_string(m_id) + "," + m_name});

View file

@ -61,6 +61,7 @@ class CWorkspace {
bool inert();
MONITORID monitorID();
PHLWINDOW getLastFocusedWindow();
PHLWINDOW getFocusCandidate();
std::string getConfigName();
bool matchesStaticSelector(const std::string& selector);
void markInert();

View file

@ -248,6 +248,7 @@ static std::expected<WindowRuleEffectValue, std::string> parseWindowRuleEffect(C
case WINDOW_RULE_EFFECT_RENDER_UNFOCUSED:
case WINDOW_RULE_EFFECT_NO_SCREEN_SHARE:
case WINDOW_RULE_EFFECT_NO_VRR:
case WINDOW_RULE_EFFECT_CONFINE_POINTER:
case WINDOW_RULE_EFFECT_STAY_FOCUSED: return truthy(raw);
case WINDOW_RULE_EFFECT_FULLSCREENSTATE: {

View file

@ -52,9 +52,9 @@ std::unordered_set<CWindowRuleEffectContainer::storageType> CWindowRuleApplicato
std::pair{std::ref(m_noFollowMouse), [this] { return noFollowMouseEffect(); }}, std::pair{std::ref(m_noScreenShare), [this] { return noScreenShareEffect(); }},
std::pair{std::ref(m_noVRR), [this] { return noVRREffect(); }}, std::pair{std::ref(m_persistentSize), [this] { return persistentSizeEffect(); }},
std::pair{std::ref(m_stayFocused), [this] { return stayFocusedEffect(); }}, std::pair{std::ref(m_idleInhibitMode), [this] { return idleInhibitModeEffect(); }},
std::pair{std::ref(m_borderSize), [this] { return borderSizeEffect(); }}, std::pair{std::ref(m_rounding), [this] { return roundingEffect(); }},
std::pair{std::ref(m_roundingPower), [this] { return roundingPowerEffect(); }}, std::pair{std::ref(m_scrollMouse), [this] { return scrollMouseEffect(); }},
std::pair{std::ref(m_scrollTouchpad), [this] { return scrollTouchpadEffect(); }},
std::pair{std::ref(m_confinePointer), [this] { return confinePointerEffect(); }}, std::pair{std::ref(m_borderSize), [this] { return borderSizeEffect(); }},
std::pair{std::ref(m_rounding), [this] { return roundingEffect(); }}, std::pair{std::ref(m_roundingPower), [this] { return roundingPowerEffect(); }},
std::pair{std::ref(m_scrollMouse), [this] { return scrollMouseEffect(); }}, std::pair{std::ref(m_scrollTouchpad), [this] { return scrollTouchpadEffect(); }},
std::pair{std::ref(m_animationStyle), [this] { return animationStyleEffect(); }}, std::pair{std::ref(m_maxSize), [this] { return maxSizeEffect(); }},
std::pair{std::ref(m_minSize), [this] { return minSizeEffect(); }}, std::pair{std::ref(m_activeBorderColor), [this] { return activeBorderColorEffect(); }},
std::pair{std::ref(m_inactiveBorderColor), [this] { return inactiveBorderColorEffect(); }}));
@ -342,6 +342,11 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
m_stayFocused.second |= rule->getPropertiesMask();
break;
}
case WINDOW_RULE_EFFECT_CONFINE_POINTER: {
m_confinePointer.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_confinePointer.second |= rule->getPropertiesMask();
break;
}
case WINDOW_RULE_EFFECT_SCROLL_MOUSE: {
m_scrollMouse.first.set(std::get<float>(value), Types::PRIORITY_WINDOW_RULE);
m_scrollMouse.second |= rule->getPropertiesMask();

View file

@ -116,6 +116,7 @@ namespace Desktop::Rule {
DEFINE_PROP(bool, noVRR, false, WINDOW_RULE_EFFECT_NO_VRR)
DEFINE_PROP(bool, persistentSize, false, WINDOW_RULE_EFFECT_PERSISTENT_SIZE)
DEFINE_PROP(bool, stayFocused, false, WINDOW_RULE_EFFECT_STAY_FOCUSED)
DEFINE_PROP(bool, confinePointer, false, WINDOW_RULE_EFFECT_CONFINE_POINTER)
DEFINE_PROP(int, idleInhibitMode, false, WINDOW_RULE_EFFECT_IDLE_INHIBIT)

View file

@ -65,12 +65,13 @@ static const std::vector<std::string> EFFECT_STRINGS = {
"scroll_mouse", //
"scroll_touchpad", //
"stay_focused", //
"confine_pointer", //
"__internal_last_static", //
};
// This is here so that if we change the rules, we get reminded to update
// the strings.
static_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 55);
static_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 56);
CWindowRuleEffectContainer::CWindowRuleEffectContainer() : IEffectContainer<eWindowRuleEffect>(std::vector<std::string>{EFFECT_STRINGS}) {
;

View file

@ -66,6 +66,7 @@ namespace Desktop::Rule {
WINDOW_RULE_EFFECT_SCROLL_MOUSE,
WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD,
WINDOW_RULE_EFFECT_STAY_FOCUSED,
WINDOW_RULE_EFFECT_CONFINE_POINTER,
WINDOW_RULE_EFFECT_LAST_STATIC,
};

View file

@ -37,6 +37,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
if (pWindow->m_isFloating) {
// if the window is floating, just bring it to the top
pWindow->m_createdOverFullscreen = true;
pWindow->updateFullscreenInputState();
g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f);
g_pHyprRenderer->damageWindow(pWindow);
return {};
@ -166,8 +167,12 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLS
return;
}
const auto PLASTWINDOW = m_focusWindow.lock();
m_focusWindow = pWindow;
if (PMONITOR && !pWindow->m_pinned)
rawMonitorFocus(PMONITOR);
const auto PLASTWINDOW = m_focusWindow.lock();
m_focusWindow = pWindow;
pWindow->m_workspace->m_lastFocusedWindow = pWindow;
/* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which
window focuses are "via keybinds" and which ones aren't. */

View file

@ -0,0 +1,184 @@
#pragma once
#include <array>
#include <cstddef>
#include "../../helpers/AnimatedVariable.hpp"
namespace Desktop::Types {
template <typename T>
struct SAddOperation {
static constexpr T identity() {
return T{};
}
static T apply(const T& lhs, const T& rhs) {
return lhs + rhs;
}
};
template <typename T>
struct SMultiplyOperation {
static constexpr T identity() {
return T{1};
}
static T apply(const T& lhs, const T& rhs) {
return lhs * rhs;
}
};
template <Animable VarType, typename Key, size_t Count, typename Operation = SMultiplyOperation<VarType>>
class CMultiAnimatedVariableContainer {
public:
using value_type = VarType;
using key_type = Key;
using operation_type = Operation;
static constexpr size_t size() {
return Count;
}
PHLANIMVAR<VarType>& get(const Key key) {
return m_vars.at(index(key));
}
const PHLANIMVAR<VarType>& get(const Key key) const {
return m_vars.at(index(key));
}
PHLANIMVAR<VarType>& operator[](const Key key) {
return get(key);
}
const PHLANIMVAR<VarType>& operator[](const Key key) const {
return get(key);
}
PHLANIMVAR<VarType>& raw(const size_t i) {
return m_vars.at(i);
}
const PHLANIMVAR<VarType>& raw(const size_t i) const {
return m_vars.at(i);
}
std::array<PHLANIMVAR<VarType>, Count>& all() {
return m_vars;
}
const std::array<PHLANIMVAR<VarType>, Count>& all() const {
return m_vars;
}
template <typename Fn>
void forEach(Fn&& fn) {
for (size_t i = 0; i < Count; ++i)
fn(static_cast<Key>(i), m_vars.at(i));
}
template <typename Fn>
void forEach(Fn&& fn) const {
for (size_t i = 0; i < Count; ++i)
fn(static_cast<Key>(i), m_vars.at(i));
}
VarType getTotal() const {
return accumulate([](const auto& var) { return var->value(); });
}
VarType getTotalGoal() const {
return accumulate([](const auto& var) { return var->goal(); });
}
VarType getTotalBegun() const {
return accumulate([](const auto& var) { return var->begun(); });
}
VarType getTotalWithout(const Key key) const {
return accumulateExcept(key, [](const auto& var) { return var->value(); });
}
VarType getTotalGoalWithout(const Key key) const {
return accumulateExcept(key, [](const auto& var) { return var->goal(); });
}
VarType getTotalBegunWithout(const Key key) const {
return accumulateExcept(key, [](const auto& var) { return var->begun(); });
}
VarType value() const {
return getTotal();
}
VarType goal() const {
return getTotalGoal();
}
bool isBeingAnimated() const {
for (const auto& var : m_vars) {
if (var && var->isBeingAnimated())
return true;
}
return false;
}
bool initialized() const {
for (const auto& var : m_vars) {
if (!var)
return false;
}
return true;
}
void warp(const bool endCallback = true) {
for (auto& var : m_vars) {
if (var)
var->warp(endCallback);
}
}
private:
static constexpr size_t index(const Key key) {
return static_cast<size_t>(key);
}
template <typename Getter>
VarType accumulate(Getter&& getter) const {
VarType result = Operation::identity();
for (const auto& var : m_vars) {
if (!var)
continue;
result = Operation::apply(result, getter(var));
}
return result;
}
template <typename Getter>
VarType accumulateExcept(const Key key, Getter&& getter) const {
VarType result = Operation::identity();
const size_t except = index(key);
for (size_t i = 0; i < Count; ++i) {
if (i == except || !m_vars.at(i))
continue;
result = Operation::apply(result, getter(m_vars.at(i)));
}
return result;
}
std::array<PHLANIMVAR<VarType>, Count> m_vars;
};
template <Animable VarType, typename Key, size_t Count, typename Operation = SMultiplyOperation<VarType>>
using CMultiAVarContainer = CMultiAnimatedVariableContainer<VarType, Key, Count, Operation>;
}

View file

@ -142,7 +142,8 @@ void CGroup::remove(PHLWINDOW w, Math::eDirection dir) {
w->m_group.reset();
removeWindowDecos(w);
w->setHidden(false);
w->setInputBlocked(INPUT_BLOCK_GROUP_INACTIVE, false);
*w->alpha(WINDOW_ALPHA_LAYOUT) = 1.F;
const bool REMOVING_GROUP = m_windows.size() <= 1;
@ -280,12 +281,16 @@ void CGroup::updateWindowVisibility() {
for (size_t i = 0; i < m_windows.size(); ++i) {
if (i == m_current) {
auto& x = m_windows.at(i);
x->setHidden(false);
x->setInputBlocked(INPUT_BLOCK_GROUP_INACTIVE, false);
*x->alpha(WINDOW_ALPHA_LAYOUT) = 1.F;
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
x->updateWindowDecos();
x->updateDecorationValues();
} else
m_windows.at(i)->setHidden(true);
} else {
auto& x = m_windows.at(i);
x->setInputBlocked(INPUT_BLOCK_GROUP_INACTIVE, true);
*x->alpha(WINDOW_ALPHA_LAYOUT) = 0.F;
}
}
m_target->recalc();

View file

@ -84,13 +84,17 @@ PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER);
g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("borderangle"), pWindow,
AVARDAMAGE_BORDER);
g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_FADE), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_ACTIVE), Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_FULLSCREEN), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_LAYOUT), Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, Config::animationTree()->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW);
g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realGlowColor, Config::animationTree()->getAnimationPropertyConfig("fadeGlow"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, Config::animationTree()->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE), Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow,
AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow,
AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, Config::animationTree()->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE);
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
@ -114,13 +118,17 @@ PHLWINDOW CWindow::create(SP<CXDGSurfaceResource> resource) {
g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER);
g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("borderangle"), pWindow,
AVARDAMAGE_BORDER);
g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_FADE), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_ACTIVE), Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_FULLSCREEN), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_LAYOUT), Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, Config::animationTree()->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW);
g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realGlowColor, Config::animationTree()->getAnimationPropertyConfig("fadeGlow"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, Config::animationTree()->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE), Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow,
AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(1.f, pWindow->alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE), Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow,
AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, Config::animationTree()->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE);
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
@ -179,7 +187,7 @@ eViewType CWindow::type() const {
}
bool CWindow::visible() const {
return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() != 0.F));
return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || m_fadingOut) && visibleByAlpha();
}
std::optional<CBox> CWindow::logicalBox() const {
@ -494,13 +502,18 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
const auto OLDWORKSPACE = m_workspace;
if (OLDWORKSPACE->isVisible()) {
m_movingToWorkspaceAlpha->setValueAndWarp(1.F);
*m_movingToWorkspaceAlpha = 0.F;
m_movingToWorkspaceAlpha->setCallbackOnEnd([this](auto) { m_monitorMovedFrom = -1; });
alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->setValueAndWarp(1.F);
*alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE) = 0.F;
alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->setCallbackOnEnd([this](auto) {
alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->setValueAndWarp(1.F);
m_monitorMovedFrom = -1;
});
m_monitorMovedFrom = OLDWORKSPACE ? OLDWORKSPACE->monitorID() : -1;
}
m_workspace = pWorkspace;
updateFullscreenInputState();
*alpha(WINDOW_ALPHA_FULLSCREEN) = isBlockedByFullscreen() ? 0.F : 1.F;
setAnimationsToMove();
@ -615,15 +628,18 @@ void CWindow::onMap() {
m_realSize->resetAllCallbacks();
m_borderFadeAnimationProgress->resetAllCallbacks();
m_borderAngleAnimationProgress->resetAllCallbacks();
m_activeInactiveAlpha->resetAllCallbacks();
m_alpha->resetAllCallbacks();
alpha(WINDOW_ALPHA_ACTIVE)->resetAllCallbacks();
alpha(WINDOW_ALPHA_FADE)->resetAllCallbacks();
alpha(WINDOW_ALPHA_FULLSCREEN)->resetAllCallbacks();
alpha(WINDOW_ALPHA_LAYOUT)->resetAllCallbacks();
m_realShadowColor->resetAllCallbacks();
m_realGlowColor->resetAllCallbacks();
m_dimPercent->resetAllCallbacks();
m_movingToWorkspaceAlpha->resetAllCallbacks();
m_movingFromWorkspaceAlpha->resetAllCallbacks();
alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->resetAllCallbacks();
alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE)->resetAllCallbacks();
m_movingFromWorkspaceAlpha->setValueAndWarp(1.F);
alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE)->setValueAndWarp(1.F);
alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->setValueAndWarp(1.F);
if (m_borderAngleAnimationProgress->enabled()) {
m_borderAngleAnimationProgress->setValueAndWarp(0.f);
@ -666,7 +682,7 @@ void CWindow::onMap() {
}
});
m_movingFromWorkspaceAlpha->setValueAndWarp(1.F);
alpha(WINDOW_ALPHA_MOVE_FROM_WORKSPACE)->setValueAndWarp(1.F);
m_reportedSize = m_pendingReportedSize;
m_animatingIn = true;
@ -709,10 +725,115 @@ void CWindow::setHidden(bool hidden) {
setSuspended(hidden);
}
bool CWindow::isHidden() {
bool CWindow::isHidden() const {
return m_hidden;
}
void CWindow::setInputBlocked(eWindowInputBlockReason reason, bool blocked) {
if (reason == INPUT_BLOCK_NONE)
return;
const auto MASK = sc<uint32_t>(reason);
if (blocked)
m_inputBlockReasons |= MASK;
else
m_inputBlockReasons &= ~MASK;
if (blocked && Desktop::focusState()->window() == m_self)
Desktop::focusState()->window().reset();
}
bool CWindow::isInputBlocked() const {
return m_inputBlockReasons != INPUT_BLOCK_NONE;
}
bool CWindow::isInputBlocked(eWindowInputBlockReason reason) const {
return (m_inputBlockReasons & sc<uint32_t>(reason)) != 0;
}
bool CWindow::isInputBlockedOnly(eWindowInputBlockReason reason) const {
return m_inputBlockReasons == sc<uint32_t>(reason);
}
bool CWindow::acceptsInput() const {
return !isHidden() && !isInputBlocked();
}
bool CWindow::isAllowedOverFullscreen() const {
if (isFullscreen() || m_pinned || m_createdOverFullscreen)
return true;
if (!m_workspace)
return false;
const auto FSWINDOW = m_workspace->getFullscreenWindow();
return FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(m_self.lock());
}
bool CWindow::isBlockedByFullscreen() const {
if (!m_workspace || !m_workspace->m_hasFullscreenWindow)
return false;
return !isAllowedOverFullscreen();
}
bool CWindow::isFadingOutUnderFullscreen() const {
return isBlockedByFullscreen() && alpha(WINDOW_ALPHA_FULLSCREEN)->isBeingAnimated() && alphaValue(WINDOW_ALPHA_FULLSCREEN) > 0.F;
}
bool CWindow::shouldRenderOverFullscreen() const {
return isAllowedOverFullscreen() || isFadingOutUnderFullscreen();
}
void CWindow::updateFullscreenInputState() {
setInputBlocked(INPUT_BLOCK_BELOW_FULLSCREEN, isBlockedByFullscreen());
}
PHLANIMVAR<float>& CWindow::alpha(eWindowAlpha type) {
return m_alpha.get(type);
}
const PHLANIMVAR<float>& CWindow::alpha(eWindowAlpha type) const {
return m_alpha.get(type);
}
float CWindow::alphaValue(eWindowAlpha type) const {
return alpha(type)->value();
}
float CWindow::alphaGoal(eWindowAlpha type) const {
return alpha(type)->goal();
}
float CWindow::alphaTotal() const {
return m_alpha.getTotal();
}
float CWindow::alphaTotalGoal() const {
return m_alpha.getTotalGoal();
}
float CWindow::alphaTotalWithout(eWindowAlpha type) const {
return m_alpha.getTotalWithout(type);
}
float CWindow::effectiveAlpha() const {
return alphaTotal();
}
bool CWindow::visibleByAlpha() const {
return effectiveAlpha() != 0.F;
}
bool CWindow::visibleByAlphaGoal() const {
return alphaTotalGoal() != 0.F;
}
bool CWindow::targetVisible() const {
return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || m_fadingOut) && visibleByAlphaGoal();
}
// check if the point is "hidden" under a rounded corner of the window
// it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize)
// otherwise behaviour is undefined
@ -762,7 +883,7 @@ Vector2D CWindow::middle() {
}
bool CWindow::opaque() {
if (m_alpha->value() != 1.f || m_activeInactiveAlpha->value() != 1.f)
if (alphaValue(WINDOW_ALPHA_FADE) != 1.f || alphaValue(WINDOW_ALPHA_FULLSCREEN) != 1.f || alphaValue(WINDOW_ALPHA_ACTIVE) != 1.f)
return false;
const auto PWORKSPACE = m_workspace;
@ -967,7 +1088,7 @@ bool CWindow::clampWindowSize(const std::optional<Vector2D> minSize, const std::
return changed;
}
bool CWindow::isFullscreen() {
bool CWindow::isFullscreen() const {
return m_fullscreenState.internal != FSMODE_NONE;
}
@ -1303,7 +1424,7 @@ PHLWINDOW CWindow::getSwallower() {
break;
for (auto const& w : g_pCompositor->m_windows) {
if (!w->m_isMapped || w->isHidden())
if (!w->m_isMapped || !w->acceptsInput())
continue;
if (w->getPID() == currentPid)
@ -1593,12 +1714,12 @@ void CWindow::updateDecorationValues() {
// opacity
const auto PWORKSPACE = m_workspace;
if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) {
*m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA);
*alpha(WINDOW_ALPHA_ACTIVE) = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA);
} else {
if (m_self == Desktop::focusState()->window())
*m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA);
*alpha(WINDOW_ALPHA_ACTIVE) = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA);
else
*m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA);
*alpha(WINDOW_ALPHA_ACTIVE) = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA);
}
// dim
@ -2056,10 +2177,10 @@ void CWindow::mapWindow() {
} else
Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);
m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA);
alpha(WINDOW_ALPHA_ACTIVE)->setValueAndWarp(*PACTIVEALPHA);
m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH);
} else {
m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA);
alpha(WINDOW_ALPHA_ACTIVE)->setValueAndWarp(*PINACTIVEALPHA);
m_dimPercent->setValueAndWarp(0);
}
@ -2129,7 +2250,7 @@ void CWindow::mapWindow() {
updateDecorationValues();
// avoid this window being visible
if (PWORKSPACE->m_hasFullscreenWindow && !isFullscreen() && !m_isFloating)
m_alpha->setValueAndWarp(0.f);
alpha(WINDOW_ALPHA_FULLSCREEN)->setValueAndWarp(0.f);
g_pCompositor->setPreferredScaleForSurface(wlSurface()->resource(), PMONITOR->m_scale);
g_pCompositor->setPreferredTransformForSurface(wlSurface()->resource(), PMONITOR->m_transform);

View file

@ -13,6 +13,7 @@
#include "../../render/decorations/IHyprWindowDecoration.hpp"
#include "../../render/Transformer.hpp"
#include "../DesktopTypes.hpp"
#include "../types/MultiAnimatedVariable.hpp"
#include "Popup.hpp"
#include "Subsurface.hpp"
#include "WLSurface.hpp"
@ -77,6 +78,24 @@ namespace Desktop::View {
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
};
enum eWindowAlpha : uint8_t {
WINDOW_ALPHA_FADE = 0,
WINDOW_ALPHA_ACTIVE,
WINDOW_ALPHA_FULLSCREEN,
WINDOW_ALPHA_LAYOUT,
WINDOW_ALPHA_MOVE_TO_WORKSPACE,
WINDOW_ALPHA_MOVE_FROM_WORKSPACE,
WINDOW_ALPHA_LAST,
};
enum eWindowInputBlockReason : uint32_t {
INPUT_BLOCK_NONE = 0,
INPUT_BLOCK_GROUP_INACTIVE = 1 << 0,
INPUT_BLOCK_MONOCLE_INACTIVE = 1 << 1,
INPUT_BLOCK_BELOW_FULLSCREEN = 1 << 2,
};
struct SWindowActiveEvent {
PHLWINDOW window = nullptr;
eFocusReason reason = sc<eFocusReason>(0) /* unknown */;
@ -194,13 +213,13 @@ namespace Desktop::View {
mutable bool m_borderSizeCacheDirty = true;
// Fade in-out
PHLANIMVAR<float> m_alpha;
bool m_fadingOut = false;
bool m_readyToDelete = false;
Vector2D m_originalClosedPos; // these will be used for calculations later on in
Vector2D m_originalClosedSize; // drawing the closing animations
SBoxExtents m_originalClosedExtents;
bool m_animatingIn = false;
Desktop::Types::CMultiAVarContainer<float, eWindowAlpha, WINDOW_ALPHA_LAST> m_alpha;
bool m_fadingOut = false;
bool m_readyToDelete = false;
Vector2D m_originalClosedPos; // these will be used for calculations later on in
Vector2D m_originalClosedSize; // drawing the closing animations
SBoxExtents m_originalClosedExtents;
bool m_animatingIn = false;
// For pinned (sticky) windows
bool m_pinned = false;
@ -225,10 +244,6 @@ namespace Desktop::View {
// Transformers
std::vector<UP<IWindowTransformer>> m_transformers;
// for alpha
PHLANIMVAR<float> m_activeInactiveAlpha;
PHLANIMVAR<float> m_movingFromWorkspaceAlpha;
// animated shadow color
PHLANIMVAR<CHyprColor> m_realShadowColor;
@ -239,8 +254,7 @@ namespace Desktop::View {
PHLANIMVAR<float> m_dimPercent;
// animate moving to an invisible workspace
int m_monitorMovedFrom = -1; // -1 means not moving
PHLANIMVAR<float> m_movingToWorkspaceAlpha;
int m_monitorMovedFrom = -1; // -1 means not moving
// swallowing
PHLWINDOWREF m_swallowed;
@ -297,7 +311,28 @@ namespace Desktop::View {
void onUnmap();
void onMap();
void setHidden(bool hidden);
bool isHidden();
bool isHidden() const;
void setInputBlocked(eWindowInputBlockReason reason, bool blocked);
bool isInputBlocked() const;
bool isInputBlocked(eWindowInputBlockReason reason) const;
bool isInputBlockedOnly(eWindowInputBlockReason reason) const;
bool acceptsInput() const;
bool isAllowedOverFullscreen() const;
bool isBlockedByFullscreen() const;
bool isFadingOutUnderFullscreen() const;
bool shouldRenderOverFullscreen() const;
void updateFullscreenInputState();
PHLANIMVAR<float>& alpha(eWindowAlpha type);
const PHLANIMVAR<float>& alpha(eWindowAlpha type) const;
float alphaValue(eWindowAlpha type) const;
float alphaGoal(eWindowAlpha type) const;
float alphaTotal() const;
float alphaTotalGoal() const;
float alphaTotalWithout(eWindowAlpha type) const;
float effectiveAlpha() const;
bool visibleByAlpha() const;
bool visibleByAlphaGoal() const;
bool targetVisible() const;
void updateDecorationValues();
SBoxExtents getFullWindowReservedArea();
Vector2D middle();
@ -313,7 +348,7 @@ namespace Desktop::View {
void activate(bool force = false);
int surfacesCount();
bool clampWindowSize(const std::optional<Vector2D> minSize, const std::optional<Vector2D> maxSize);
bool isFullscreen();
bool isFullscreen() const;
bool isEffectiveInternalFSMode(const eFullscreenMode) const;
int getRealBorderSize() const;
float getScrollMouse();
@ -401,9 +436,10 @@ namespace Desktop::View {
void unmanagedSetGeometry();
// For hidden windows and stuff
bool m_hidden = false;
bool m_suspended = false;
WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID;
bool m_hidden = false;
bool m_suspended = false;
WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID;
uint32_t m_inputBlockReasons = INPUT_BLOCK_NONE;
};
inline bool valid(PHLWINDOW w) {

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include <string>
#include <set>
#include "../helpers/signal/Signal.hpp"
enum eHIDCapabilityType : uint8_t {
@ -36,6 +37,7 @@ class IHID {
CSignalT<> destroy;
} m_events;
std::string m_deviceName;
std::string m_hlName;
std::string m_deviceName;
std::string m_hlName;
std::set<std::string> m_deviceTags;
};

View file

@ -523,54 +523,6 @@ std::string execAndGet(const char* cmd) {
return proc.stdOut();
}
void logSystemInfo() {
struct utsname unameInfo;
uname(&unameInfo);
Log::logger->log(Log::DEBUG, "System name: {}", std::string{unameInfo.sysname});
Log::logger->log(Log::DEBUG, "Node name: {}", std::string{unameInfo.nodename});
Log::logger->log(Log::DEBUG, "Release: {}", std::string{unameInfo.release});
Log::logger->log(Log::DEBUG, "Version: {}", std::string{unameInfo.version});
Log::logger->log(Log::DEBUG, "\n");
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | grep -F -A4 vga");
#elif defined(__arm__) || defined(__aarch64__)
std::string GPUINFO;
const std::filesystem::path dev_tree = "/proc/device-tree";
try {
if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {
std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {
if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with("soc")) {
std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {
if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with("gpu")) {
std::filesystem::path file_path = sub_entry.path() / "compatible";
std::ifstream file(file_path);
if (file)
GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
});
}
});
}
} catch (...) { GPUINFO = "error"; }
#else
const std::string GPUINFO = execAndGet("lspci -vnn | grep -E '(VGA|Display|3D)'");
#endif
Log::logger->log(Log::DEBUG, "GPU information:\n{}\n", GPUINFO);
if (GPUINFO.contains("NVIDIA")) {
Log::logger->log(Log::WARN, "Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\n");
}
// log etc
Log::logger->log(Log::DEBUG, "os-release:");
Log::logger->log(Log::DEBUG, "{}", NFsUtils::readFileAsString("/etc/os-release").value_or("error"));
}
int64_t getPPIDof(int64_t pid) {
#if defined(KERN_PROC_PID)
int mib[] = {

View file

@ -27,7 +27,6 @@ bool isDirection(const char&);
SWorkspaceIDName getWorkspaceIDNameFromString(const std::string&);
std::optional<std::string> cleanCmdForWorkspace(const std::string&, std::string);
float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2);
void logSystemInfo();
std::string execAndGet(const char*);
int64_t getPPIDof(int64_t pid);
std::expected<int64_t, std::string> configStringToInt(const std::string&);

View file

@ -7,12 +7,10 @@
#include "../protocols/ColorManagement.hpp"
#include "../Compositor.hpp"
#include "../config/ConfigValue.hpp"
#include "../config/ConfigManager.hpp"
#include "../config/shared/monitor/MonitorRuleManager.hpp"
#include "../config/shared/workspace/WorkspaceRuleManager.hpp"
#include "../config/shared/animation/AnimationTree.hpp"
#include "../protocols/GammaControl.hpp"
#include "../devices/ITouch.hpp"
#include "../protocols/LayerShell.hpp"
#include "../protocols/PresentationTime.hpp"
#include "../protocols/DRMLease.hpp"
@ -34,7 +32,6 @@
#include "../layout/LayoutManager.hpp"
#include "../i18n/Engine.hpp"
#include "../helpers/cm/ColorManagement.hpp"
#include "sync/SyncTimeline.hpp"
#include "time/Time.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/state/FocusState.hpp"
@ -799,9 +796,10 @@ bool CMonitor::applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force)
}
}
const auto WAS10B = m_enabled10bit;
const auto OLDRES = m_pixelSize;
bool success = false;
const auto WAS10B = m_enabled10bit;
const auto OLDPIXELSIZE = m_pixelSize;
const auto OLDTRANSFORMEDSIZE = m_transformedSize;
bool success = false;
// Needed in case we are switching from a custom modeline to a standard mode
m_customDrmMode = {};
@ -1067,13 +1065,18 @@ bool CMonitor::applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force)
updateMatrix();
if ((WAS10B != m_enabled10bit || OLDRES != m_pixelSize)) {
if ((WAS10B != m_enabled10bit || OLDPIXELSIZE != m_pixelSize)) {
m_resources.reset(); // TODO skip for 10bit change and fp16?
if (g_pHyprRenderer && g_pHyprRenderer->glBackend())
g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self);
}
if (m_background && (OLDPIXELSIZE != m_pixelSize || OLDTRANSFORMEDSIZE != m_transformedSize)) {
Log::logger->log(Log::DEBUG, "{} reset BGTex: pixelSize {} -> {}, transformedSize {} -> {}", m_name, OLDPIXELSIZE, m_pixelSize, OLDTRANSFORMEDSIZE, m_transformedSize);
m_background.reset();
}
g_pCompositor->scheduleMonitorStateRecheck();
m_damage.setSize(m_transformedSize);
@ -1403,10 +1406,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING);
if (!pWindow)
pWindow = pWorkspace->getTopLeftWindow();
if (!pWindow)
pWindow = pWorkspace->getFirstWindow();
pWindow = pWorkspace->getFocusCandidate();
}
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
@ -1564,7 +1564,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
if (VECNOTINRECT(MIDDLE, PMONFROMMIDDLE->m_position.x, PMONFROMMIDDLE->m_position.y, PMONFROMMIDDLE->m_position.x + PMONFROMMIDDLE->m_size.x,
PMONFROMMIDDLE->m_position.y + PMONFROMMIDDLE->m_size.y)) {
// not on any monitor, center
pos = middle() / 2.f - w->m_realSize->goal() / 2.f;
pos = middle() - w->m_realSize->goal() / 2.f;
} else
pos = pos - PMONFROMMIDDLE->m_position + m_position;
@ -1755,10 +1755,10 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) {
}
for (auto const& w : g_pCompositor->m_windows) {
if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden())
if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || !w->visible())
continue;
if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(m_self.lock())) {
if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->isAllowedOverFullscreen() && w->visibleOnMonitor(m_self.lock())) {
reasons |= SC_FLOAT;
if (!full)
return reasons;

View file

@ -7,11 +7,27 @@
#include "desktop/DesktopTypes.hpp"
#include "render/Renderer.hpp"
void CMonitorZoomController::pinAnchor(const Vector2D& anchor) {
m_pinnedAnchor = anchor;
m_anchorPinned = true;
}
void CMonitorZoomController::clearAnchor() {
m_anchorPinned = false;
}
Vector2D CMonitorZoomController::getAnchor(const PHLMONITORREF& monitor) {
if (m_anchorPinned)
return m_pinnedAnchor;
return g_pInputManager->getMouseCoordsInternal() - monitor->m_position;
}
void CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData) {
const auto m = m_renderData.pMonitor;
auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y);
const auto ZOOM = g_pHyprRenderer->m_renderData.mouseZoomFactor;
const auto MOUSE = g_pInputManager->getMouseCoordsInternal() - m->m_position;
const auto MOUSE = getAnchor(m);
if (m_lastZoomLevel != ZOOM) {
if (m_resetCameraState) {
@ -83,8 +99,7 @@ void CMonitorZoomController::applyZoomTransform(CBox& monbox, const Render::SRen
if (*PZOOMDETACHEDCAMERA && !INITANIM)
zoomWithDetachedCamera(monbox, m_renderData);
else {
const auto ZOOMCENTER =
g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f;
const auto ZOOMCENTER = g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? getAnchor(m) * m->m_scale : m->m_transformedSize / 2.f;
monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER);
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "./math/Math.hpp"
#include "../desktop/DesktopTypes.hpp"
namespace Render {
struct SRenderData;
@ -10,12 +11,18 @@ class CMonitorZoomController {
public:
bool m_resetCameraState = true;
void pinAnchor(const Vector2D& anchor);
void clearAnchor();
void applyZoomTransform(CBox& monbox, const Render::SRenderData& m_renderData);
private:
void zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData);
void zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData);
Vector2D getAnchor(const PHLMONITORREF& monitor);
CBox m_camera;
float m_lastZoomLevel = 1.0f;
bool m_padCamEdges = true;
CBox m_camera;
Vector2D m_pinnedAnchor = {};
float m_lastZoomLevel = 1.0f;
bool m_padCamEdges = true;
bool m_anchorPinned = false;
};

View file

@ -58,49 +58,22 @@ namespace NSplashes {
"Thanks ThatOneCalculator!",
"The AUR packages always work, except for the times they don't.",
"Funny animation compositor woo",
"3 years!",
"4 years!",
"Beauty will save the world", // 4th ricing comp winner - zacoons' choice
// music reference / quote section
"J'remue le ciel, le jour, la nuit.",
"aezakmi, aezakmi, aezakmi, aezakmi, aezakmi, aezakmi, aezakmi!",
"Wir sind schon sehr lang zusammen...",
"I see a red door and I want it painted black.",
"Take on me, take me on...",
"You spin me right round baby right round",
"Stayin' alive, stayin' alive",
"Say no way, say no way ya, no way!",
"Ground control to Major Tom...",
"Alors on danse",
"And all that I can see, is just a yellow lemon tree.",
"Got a one-way ticket to the blues",
"Is this the real life, is this just fantasy",
"What's in your head, in your head?",
"We're all living in America, America, America.",
"I'm still standing, better than I ever did",
"Here comes the sun, bringing you love and shining on everyone",
"Two trailer park girls go round the outside",
"With the lights out, it's less dangerous",
"Here we go back, this is the moment, tonight is the night",
"Now you're just somebody that I used to know...",
"Black bird, black moon, black sky",
"Some legends are told, some turn to dust or to gold",
"Your brain gets smart, but your head gets dumb.",
"Save your mercy for someone who needs it more",
"You're gonna hear my voice when I shout it out loud",
"Ding ding pch n daa, bam-ba-ba-re-bam baram bom bom baba-bam-bam-bommm",
"Súbeme la radio que esta es mi canción",
"I'm beggin', beggin' you",
"Never gonna let you down (I am trying!)",
"Hier kommt die Sonne",
"Kickstart my heart, give it a start",
"Fear of the dark, I have a constant fear that something's always near",
"Komm mit, reih dich ein.",
"I wish I had an angel for one moment of love",
"We're the children of the dark",
"You float like a feather, in a beautiful world",
"Demons come at night and they bring the end",
"All I wanna say is that they don't really care about us",
"Has he lost his mind? Can he see or is he blind?",
"Configration",
"RIP hyprlang",
"better call vaxry",
"i hypr therefore i land",
"five. hundred. config errors.",
"bundled with anime girls for your convenience",
"now with 200% more hypr and land",
"daily dose of rice",
"Removed Herobrine",
"You should try quickshell!",
"He was an X11 boy, she was a Wayland girl...",
"How do I exit vim????",
"Now with lua, it might as well be awesome.",
"Have you ran your daily fastfetch yet?"
// clang-format on
};

257
src/helpers/SystemInfo.cpp Normal file
View file

@ -0,0 +1,257 @@
#include "SystemInfo.hpp"
#include "../Compositor.hpp"
#include "../version.h"
#include "../plugins/PluginAPI.hpp"
#include "../plugins/PluginSystem.hpp"
#include "../render/OpenGL.hpp"
#include "../config/ConfigManager.hpp"
#include <hyprutils/string/String.hpp>
#include <sys/utsname.h>
#include <fstream>
#include <sstream>
#include <format>
#include <algorithm>
using namespace Helpers::SystemInfo;
using namespace Helpers;
using namespace Hyprutils::String;
using namespace Render::GL;
static void trimTrailingComma(std::string& str) {
if (!str.empty() && str.back() == ',')
str.pop_back();
}
std::string SystemInfo::getStatus(eHyprCtlOutputFormat fmt) {
Aquamarine::eBackendType backendType = Aquamarine::eBackendType::AQ_BACKEND_NULL;
for (const auto& i : g_pCompositor->m_aqBackend->getImplementations()) {
if (i->type() == Aquamarine::eBackendType::AQ_BACKEND_NULL || i->type() == Aquamarine::eBackendType::AQ_BACKEND_HEADLESS)
continue;
backendType = i->type();
break;
}
std::string backendStr;
switch (backendType) {
case Aquamarine::AQ_BACKEND_DRM: backendStr = "drm"; break;
case Aquamarine::AQ_BACKEND_WAYLAND: backendStr = "wayland"; break;
default: backendStr = "error"; break;
}
if (fmt == eHyprCtlOutputFormat::FORMAT_JSON) {
return std::format(R"#(
{{
"configProvider": "{}",
"backend": "{}"
}}
)#",
Config::typeToString(Config::mgr()->type()), backendStr);
}
return std::format(R"#(
configProvider: {}
backend: {}
)#",
Config::typeToString(Config::mgr()->type()), backendStr);
}
std::string SystemInfo::getVersion(eHyprCtlOutputFormat fmt) {
auto commitMsg = trim(GIT_COMMIT_MESSAGE);
std::ranges::replace(commitMsg, '#', ' ');
if (fmt == eHyprCtlOutputFormat::FORMAT_NORMAL) {
std::string result = std::format("Hyprland {} built from branch {} at commit {} {} ({}).\n"
"Date: {}\n"
"Tag: {}, commits: {}\n",
HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS);
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "Version ABI string: ";
result += __hyprland_api_get_hash();
result += "\n";
#if (!ISDEBUG && !defined(NO_XWAYLAND) && !defined(BUILT_WITH_NIX))
result += "no flags were set\n";
#else
result += "flags set:\n";
#if ISDEBUG
result += "debug\n";
#endif
#ifdef NO_XWAYLAND
result += "no xwayland\n";
#endif
#ifdef BUILT_WITH_NIX
result += "nix\n";
#endif
#endif
return result;
} else {
std::string result = std::format(
R"#({{
"branch": "{}",
"commit": "{}",
"version": "{}",
"dirty": {},
"commit_message": "{}",
"commit_date": "{}",
"tag": "{}",
"commits": "{}",
"buildAquamarine": "{}",
"buildHyprlang": "{}",
"buildHyprutils": "{}",
"buildHyprcursor": "{}",
"buildHyprgraphics": "{}",
"systemAquamarine": "{}",
"systemHyprlang": "{}",
"systemHyprutils": "{}",
"systemHyprcursor": "{}",
"systemHyprgraphics": "{}",
"abiHash": "{}",
"flags": [)#",
GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (GIT_DIRTY == std::string_view{"dirty"} ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG,
GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION, getSystemLibraryVersion("aquamarine"),
getSystemLibraryVersion("hyprlang"), getSystemLibraryVersion("hyprutils"), getSystemLibraryVersion("hyprcursor"), getSystemLibraryVersion("hyprgraphics"),
__hyprland_api_get_hash());
#if ISDEBUG
result += "\"debug\",";
#endif
#ifdef NO_XWAYLAND
result += "\"no xwayland\",";
#endif
#ifdef BUILT_WITH_NIX
result += "\"nix\",";
#endif
trimTrailingComma(result);
result += "]\n}";
return result;
}
return ""; // make the compiler happy
}
std::string SystemInfo::getSystemInfo() {
std::string result = getVersion(eHyprCtlOutputFormat::FORMAT_NORMAL);
static auto check = [](bool y) -> std::string { return y ? "✔️" : ""; };
static auto backend = [](Aquamarine::eBackendType t) -> std::string {
switch (t) {
case Aquamarine::AQ_BACKEND_DRM: return "drm";
case Aquamarine::AQ_BACKEND_HEADLESS: return "headless";
case Aquamarine::AQ_BACKEND_WAYLAND: return "wayland";
default: break;
}
return "?";
};
result += "\n\nSystem Information:\n";
struct utsname unameInfo;
uname(&unameInfo);
result += "System name: " + std::string{unameInfo.sysname} + "\n";
result += "Node name: " + std::string{unameInfo.nodename} + "\n";
result += "Release: " + std::string{unameInfo.release} + "\n";
result += "Version: " + std::string{unameInfo.version} + "\n";
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "\n\n";
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | grep -F -A4 vga");
#elif defined(__arm__) || defined(__aarch64__)
std::string GPUINFO;
const std::filesystem::path dev_tree = "/proc/device-tree";
try {
if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {
std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {
if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with("soc")) {
std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {
if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with("gpu")) {
std::filesystem::path file_path = sub_entry.path() / "compatible";
std::ifstream file(file_path);
if (file)
GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
});
}
});
}
} catch (...) { GPUINFO = "error"; }
#else
const std::string GPUINFO = execAndGet("lspci -vnn | grep -E '(VGA|Display|3D)'");
#endif
result += "GPU information: \n" + GPUINFO;
if (GPUINFO.contains("NVIDIA") && std::filesystem::exists("/proc/driver/nvidia/version")) {
std::ifstream file("/proc/driver/nvidia/version");
std::string line;
if (file.is_open()) {
while (std::getline(file, line)) {
if (!line.contains("NVRM"))
continue;
result += line;
result += "\n";
}
} else
result += "error";
}
result += "\n\n";
if (std::ifstream file("/etc/os-release"); file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
result += "os-release: " + buffer.str() + "\n\n";
} else
result += "os-release: error\n\n";
result += "plugins:\n";
if (g_pPluginSystem) {
for (auto const& pl : g_pPluginSystem->getAllPlugins()) {
result += std::format(" {} by {} ver {}\n", pl->m_name, pl->m_author, pl->m_version);
}
} else
result += "\tunknown: not runtime\n";
if (g_pHyprOpenGL) {
result += std::format("\nExplicit sync: {}", g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext ? "supported" : "missing");
result += std::format("\nGL ver: {}", g_pHyprOpenGL->m_eglContextVersion == CHyprOpenGLImpl::EGL_CONTEXT_GLES_3_2 ? "3.2" : "3.0");
}
if (g_pCompositor) {
result += std::format("\nBackend: {}", g_pCompositor->m_aqBackend->hasSession() ? "drm" : "sessionless");
result += "\n\nMonitor info:";
for (const auto& m : g_pCompositor->m_monitors) {
result += std::format("\n\tPanel {}: {}x{}, {} {} {} {} -> backend {}\n\t\texplicit {}\n\t\tedid:\n\t\t\thdr {}\n\t\t\tchroma {}\n\t\t\tbt2020 {}\n\t\tvrr capable "
"{}\n\t\tnon-desktop {}\n\t\t",
m->m_name, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_output->name, m->m_output->make, m->m_output->model, m->m_output->serial,
backend(m->m_output->getBackend()->type()), check(m->m_output->supportsExplicit), check(m->m_output->parsedEDID.hdrMetadata.has_value()),
check(m->m_output->parsedEDID.chromaticityCoords.has_value()), check(m->m_output->parsedEDID.supportsBT2020), check(m->m_output->vrrCapable),
check(m->m_output->nonDesktop));
}
}
result += "\n\nState:\n";
result += getStatus(FORMAT_NORMAL);
result += "\n\n";
return result;
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <string>
#include "../SharedDefs.hpp"
namespace Helpers::SystemInfo {
std::string getSystemInfo();
std::string getVersion(eHyprCtlOutputFormat fmt);
std::string getStatus(eHyprCtlOutputFormat fmt);
};

View file

@ -38,6 +38,15 @@ bool CTagKeeper::applyTag(const std::string& tag, bool dynamic) {
return true;
}
bool CTagKeeper::clearTags() {
if (!m_tags.empty()) {
m_tags.clear();
return true;
}
return false;
}
bool CTagKeeper::removeDynamicTag(const std::string& s) {
return std::erase_if(m_tags, [&s](const auto& tag) { return tag == s + "*"; });
}

View file

@ -8,6 +8,7 @@ class CTagKeeper {
bool isTagged(const std::string& tag, bool strict = false) const;
bool applyTag(const std::string& tag, bool dynamic = false);
bool removeDynamicTag(const std::string& tag);
bool clearTags();
const auto& getTags() const {
return m_tags;

View file

@ -217,7 +217,7 @@ void CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP<I
const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom;
for (auto& other : g_pCompositor->m_windows) {
if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut ||
if ((HASFULLSCREEN && !other->isAllowedOverFullscreen()) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut ||
other->isX11OverrideRedirect())
continue;

View file

@ -729,7 +729,8 @@ Config::ErrorResult CDwindleAlgorithm::layoutMsg(const std::string_view& sv) {
CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F);
CURRENT_NODE->pParent->recalcSizePosRecursive();
}
} else
return Config::configError(std::format("Unknown dwindle layoutmsg: {}", sv), Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT);
return {};
}

View file

@ -476,10 +476,12 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
return stateErr("no master node");
const auto NEWCHILD = PMASTER->pTarget.lock();
if (!NEWCHILD)
return stateErr("master target expired");
const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; });
if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {
if (NEWCHILD != PWINDOW->layoutTarget()) {
const auto& NEWMASTER = PWINDOW->layoutTarget();
const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child";
g_layoutManager->switchTargets(NEWMASTER, NEWCHILD);
@ -516,8 +518,12 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
const auto& ARG = vars[1]; // returns empty string if out of bounds
if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) {
switchToWindow(PMASTER->pTarget.lock());
const auto TARGET = PMASTER->pTarget.lock();
if (!TARGET)
return stateErr("master target expired");
if (TARGET != PWINDOW->layoutTarget()) {
switchToWindow(TARGET);
// save previously focused window (only for `previous` mode)
if (ARG == "previous")
m_workspaceData.focusMasterPrev = PWINDOW->layoutTarget();
@ -709,12 +715,15 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
if (!OLDMASTER)
return stateErr("no old master");
auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
SP<ITarget> newFocus;
for (auto& nd : m_masterNodesData) {
if (!nd->isMaster) {
const auto& newMaster = nd;
newMaster->isMaster = true;
newFocus = newMaster->pTarget.lock();
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
@ -723,7 +732,6 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
else if (newMasterIt > oldMasterIt)
std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));
switchToWindow(newMaster->pTarget.lock());
OLDMASTER->isMaster = false;
oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
@ -735,6 +743,8 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
}
calculateWorkspace();
if (newFocus)
switchToWindow(newFocus);
} else if (command == "rollprev") {
const auto PNODE = getNodeFromWindow(PWINDOW);
@ -745,12 +755,15 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
if (!OLDMASTER)
return stateErr("no old master");
auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
SP<ITarget> newFocus;
for (auto& nd : m_masterNodesData | std::views::reverse) {
if (!nd->isMaster) {
const auto& newMaster = nd;
newMaster->isMaster = true;
newFocus = newMaster->pTarget.lock();
auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster);
@ -759,7 +772,6 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
else if (newMasterIt > oldMasterIt)
std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt));
switchToWindow(newMaster->pTarget.lock());
OLDMASTER->isMaster = false;
oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER);
@ -771,7 +783,10 @@ Config::ErrorResult CMasterAlgorithm::layoutMsg(const std::string_view& sv) {
}
calculateWorkspace();
}
if (newFocus)
switchToWindow(newFocus);
} else
return Config::configError(std::format("Unknown master layoutmsg: {}", sv), Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT);
return {};
}

View file

@ -46,8 +46,10 @@ CMonocleAlgorithm::~CMonocleAlgorithm() {
continue;
const auto WINDOW = TARGET->window();
if (WINDOW)
WINDOW->setHidden(false);
if (WINDOW) {
WINDOW->setInputBlocked(Desktop::View::INPUT_BLOCK_MONOCLE_INACTIVE, false);
*WINDOW->alpha(Desktop::View::WINDOW_ALPHA_LAYOUT) = 1.F;
}
}
m_focusCallback.reset();
@ -81,8 +83,10 @@ void CMonocleAlgorithm::removeTarget(SP<ITarget> target) {
// unhide window when removing from monocle layout
const auto WINDOW = target->window();
if (WINDOW)
WINDOW->setHidden(false);
if (WINDOW) {
WINDOW->setInputBlocked(Desktop::View::INPUT_BLOCK_MONOCLE_INACTIVE, false);
*WINDOW->alpha(Desktop::View::WINDOW_ALPHA_LAYOUT) = 1.F;
}
const auto INDEX = std::distance(m_targetDatas.begin(), it);
m_targetDatas.erase(it);
@ -142,7 +146,8 @@ void CMonocleAlgorithm::recalculate() {
TARGET->setPositionGlobal(WORK_AREA);
const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex);
WINDOW->setHidden(!SHOULD_BE_VISIBLE);
WINDOW->setInputBlocked(Desktop::View::INPUT_BLOCK_MONOCLE_INACTIVE, !SHOULD_BE_VISIBLE);
*WINDOW->alpha(Desktop::View::WINDOW_ALPHA_LAYOUT) = SHOULD_BE_VISIBLE ? 1.F : 0.F;
}
}

View file

@ -48,7 +48,7 @@ bool CDragStateController::updateDragWindow() {
const auto PWORKSPACE = DRAGGINGTARGET->workspace();
const auto DRAGGINGWINDOW = DRAGGINGTARGET->window();
if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) {
if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || !DRAGGINGWINDOW->isAllowedOverFullscreen())) {
Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)");
CKeybindManager::changeMouseBindMode(MBIND_INVALID);
return true;

View file

@ -100,14 +100,15 @@ int main(int argc, char** argv) {
return 1;
}
const auto FD_STR = *std::next(it);
try {
socketFd = std::stoi(*std::next(it));
socketFd = std::stoi(FD_STR);
// check if socketFd is a valid file descriptor
if (fcntl(socketFd, F_GETFD) == -1)
throw std::exception();
} catch (...) {
std::println(stderr, "[ ERROR ] Invalid Wayland FD!");
throw std::runtime_error("invalid or closed file descriptor");
} catch (const std::exception& e) {
std::println(stderr, "[ ERROR ] (main.cpp:{}) | Invalid Wayland FD '{}': {}!", __LINE__, FD_STR, e.what());
help();
return 1;
@ -123,13 +124,14 @@ int main(int argc, char** argv) {
configPath = *std::next(it);
try {
configPath = std::filesystem::canonical(configPath);
const auto ABS_PATH = std::filesystem::canonical(configPath);
if (!std::filesystem::is_regular_file(configPath)) {
throw std::exception();
if (!std::filesystem::is_regular_file(ABS_PATH)) {
throw std::runtime_error("not a regular file");
}
} catch (...) {
std::println(stderr, "[ ERROR ] Config file '{}' doesn't exist!", configPath);
configPath = ABS_PATH;
} catch (const std::exception& e) {
std::println(stderr, "[ ERROR ] (main.cpp:{}) | Config file '{}' is invalid: {}!", __LINE__, configPath, e.what());
help();
return 1;
@ -165,11 +167,12 @@ int main(int argc, char** argv) {
return 1;
}
const auto WATCHDOG_STR = *std::next(it);
try {
watchdogFd = std::stoi(*std::next(it));
watchdogFd = std::stoi(WATCHDOG_STR);
it++;
} catch (...) {
std::println(stderr, "[ ERROR ] Invalid fd for watchdog fd");
} catch (const std::exception& e) {
std::println(stderr, "[ ERROR ] (main.cpp:{}) | Invalid watchdog FD '{}': {}!", __LINE__, WATCHDOG_STR, e.what());
help();
return 1;
}
@ -199,8 +202,41 @@ int main(int argc, char** argv) {
return 1;
}
if (!verifyConfig)
if (!verifyConfig) {
std::println("Welcome to Hyprland!");
std::println(R"#(
YY UJ
YYY UUJ
XXXY UUUU
zXXXX UUUUU
zzzzX UUUUJ
cczzz UUUUJ
vccccz UUUUUJ
vvcccc UUUUUJ
vvvvv UUUUJ
uuuvv UUUUJ
uuuuu UUUUU
nnnuu UUUUU
nnnnn YUUUU
xxnn YUUU
xxxn YYUU
xxxx YYUU
rxxx YYYY
rrrx YYYY
rrrx XXXY
rrrr XXXX
rrrr zzXX
rrrr zzzz
rrrrr ccczz
rrrrrx vccccc
rrrrxxxx uuvvvvvc
rrxxxxxxnnnnuuuuuv
xxxxxnnnnu
)#");
}
// let's init the compositor.
// it initializes basic Wayland stuff in the constructor.

View file

@ -79,7 +79,7 @@ CCursorManager::CCursorManager() {
if (SIZE) {
try {
m_size = std::stoi(SIZE);
} catch (...) { ; }
} catch (...) { Log::logger->log(Log::WARN, "Invalid HYPRCURSOR_SIZE value \"{}\"", SIZE); }
}
if (m_size <= 0) {
@ -93,7 +93,7 @@ CCursorManager::CCursorManager() {
if (SIZE) {
try {
m_size = std::stoi(SIZE);
} catch (...) { ; }
} catch (...) { Log::logger->log(Log::WARN, "Invalid XCURSOR_SIZE value \"{}\"", SIZE); }
}
if (m_size <= 0) {

View file

@ -505,25 +505,44 @@ void CKeybindManager::onSwitchOffEvent(const std::string& switchName) {
handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:off:" + switchName}, true, nullptr, nullptr);
}
eMultiKeyCase CKeybindManager::mkKeysymSetMatches(const std::vector<xkb_keysym_t> keybindKeysyms, const std::set<xkb_keysym_t> pressedKeysyms) {
// Returns whether two sets of keysyms are equal, partially equal, or not
// matching. (Partially matching means that pressed is a subset of bound)
eMultiKeyCase CKeybindManager::mkKeysymSetMatches(const std::vector<KeybindKey>& keybindKeysyms, const std::set<KeybindKey>& pressedKeysyms) {
// Returns whether the bound and pressed keys match fully, partially, or not at all.
// KeybindKey stores {keysym, keycode}; either non-zero field matching is enough.
std::set<xkb_keysym_t> boundKeysNotPressed;
std::set<xkb_keysym_t> pressedKeysNotBound;
const auto MATCHES = [](const KeybindKey& lhs, const KeybindKey& rhs) {
return (lhs.first != 0 && rhs.first != 0 && lhs.first == rhs.first) || (lhs.second != 0 && rhs.second != 0 && lhs.second == rhs.second);
};
std::set<xkb_keysym_t> symsKb;
for (const auto& k : keybindKeysyms) {
symsKb.emplace(k);
std::vector<KeybindKey> pressed{pressedKeysyms.begin(), pressedKeysyms.end()};
std::vector<int> boundForPressed(pressed.size(), -1);
const auto tryMatch = [&](auto&& self, const size_t boundIdx, std::vector<uint8_t>& seen) -> bool {
for (size_t pressedIdx = 0; pressedIdx < pressed.size(); ++pressedIdx) {
if (seen[pressedIdx] || !MATCHES(keybindKeysyms[boundIdx], pressed[pressedIdx]))
continue;
seen[pressedIdx] = true;
if (boundForPressed[pressedIdx] == -1 || self(self, static_cast<size_t>(boundForPressed[pressedIdx]), seen)) {
boundForPressed[pressedIdx] = static_cast<int>(boundIdx);
return true;
}
}
return false;
};
size_t matches = 0;
for (size_t boundIdx = 0; boundIdx < keybindKeysyms.size(); ++boundIdx) {
std::vector<uint8_t> seen(pressed.size(), false);
if (tryMatch(tryMatch, boundIdx, seen))
++matches;
}
std::ranges::set_difference(symsKb, pressedKeysyms, std::inserter(boundKeysNotPressed, boundKeysNotPressed.begin()));
std::ranges::set_difference(pressedKeysyms, symsKb, std::inserter(pressedKeysNotBound, pressedKeysNotBound.begin()));
if (boundKeysNotPressed.empty() && pressedKeysNotBound.empty())
if (matches == keybindKeysyms.size() && matches == pressed.size())
return MK_FULL_MATCH;
if (!boundKeysNotPressed.empty() && pressedKeysNotBound.empty())
if (matches > 0 || (pressed.empty() && !keybindKeysyms.empty()))
return MK_PARTIAL_MATCH;
return MK_NO_MATCH;
@ -556,14 +575,14 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
if (key.keysym != 0) {
if (pressed) {
if (keycodeToModifier(key.keycode))
m_mkMods.insert(key.keysym);
m_mkMods.emplace(key.keysym, key.keycode);
else
m_mkKeys.insert(key.keysym);
m_mkKeys.emplace(key.keysym, key.keycode);
} else {
if (keycodeToModifier(key.keycode))
m_mkMods.erase(key.keysym);
std::erase_if(m_mkMods, [&key](const auto& e) { return e.first == key.keysym || e.second == key.keycode; });
else
m_mkKeys.erase(key.keysym);
std::erase_if(m_mkKeys, [&key](const auto& e) { return e.first == key.keysym || e.second == key.keycode; });
}
}
@ -586,7 +605,12 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
continue;
if (device) {
if (k->deviceInclusive ^ k->devices.contains(device->m_hlName))
bool isTagValid = false;
for (const auto& tag : device->m_deviceTags) {
if (k->devices.contains(tag))
isTagValid = true;
}
if (k->deviceInclusive ^ (k->devices.contains(device->m_hlName) || isTagValid))
continue;
}
@ -615,7 +639,8 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
// check for just the one match
// this is also needed for multi-key binds so that SUPER + A + K can't
// be actuated by SUPER + K + A
if (key.keysym != k->sMkKeys.back())
auto& back = k->sMkKeys.back();
if (key.keysym != back.first && key.keycode != back.second)
continue;
} else if (!key.keyName.empty()) {
if (key.keyName != k->key)
@ -653,7 +678,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
}
if (pressed && k->release && !SPECIALDISPATCHER) {
if (k->nonConsuming)
if (k->nonConsuming || k->autoConsuming)
continue;
found = true; // suppress the event
@ -672,7 +697,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
continue;
} else if (!k->release && !SPECIALDISPATCHER) {
if (k->nonConsuming)
if (k->nonConsuming || k->autoConsuming)
continue;
found = true; // suppress the event
@ -737,7 +762,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
m_repeatKeyTimer->updateTimeout(std::chrono::milliseconds(KEEB->m_repeatDelay));
}
if (!k->nonConsuming)
if (!k->nonConsuming && !(k->autoConsuming && !res.success))
found = true;
}

View file

@ -25,13 +25,15 @@ struct SSubmap {
}
};
using KeybindKey = std::pair<xkb_keysym_t, xkb_keycode_t>;
struct SKeybind {
std::string key = "";
std::vector<xkb_keysym_t> sMkKeys = {};
std::vector<KeybindKey> sMkKeys = {};
uint32_t keycode = 0;
bool catchAll = false;
uint32_t modmask = 0;
std::vector<xkb_keysym_t> sMkMods = {};
std::vector<KeybindKey> sMkMods = {};
std::string handler = "";
std::string arg = "";
bool locked = false;
@ -42,6 +44,7 @@ struct SKeybind {
bool longPress = false;
bool mouse = false;
bool nonConsuming = false;
bool autoConsuming = false;
bool transparent = false;
bool ignoreMods = false;
bool multiKey = false;
@ -155,10 +158,10 @@ class CKeybindManager {
SDispatchResult handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool, SP<IKeyboard>, SP<IHID>);
std::set<xkb_keysym_t> m_mkKeys = {};
std::set<xkb_keysym_t> m_mkMods = {};
std::set<KeybindKey> m_mkKeys = {};
std::set<KeybindKey> m_mkMods = {};
eMultiKeyCase mkBindMatches(const SP<SKeybind>);
eMultiKeyCase mkKeysymSetMatches(const std::vector<xkb_keysym_t>, const std::set<xkb_keysym_t>);
eMultiKeyCase mkKeysymSetMatches(const std::vector<KeybindKey>&, const std::set<KeybindKey>&);
bool handleInternalKeybinds(xkb_keysym_t);
bool handleVT(xkb_keysym_t);

View file

@ -97,7 +97,9 @@ void CProtocolManager::onMonitorModeChange(PHLMONITOR pMonitor) {
else if (!ISMIRROR && (!PROTO::outputs.contains(pMonitor->m_name) || PROTO::outputs.at(pMonitor->m_name)->isDefunct())) {
if (PROTO::outputs.contains(pMonitor->m_name))
PROTO::outputs.erase(pMonitor->m_name);
PROTO::outputs.emplace(pMonitor->m_name, makeShared<CWLOutputProtocol>(&wl_output_interface, 4, std::format("WLOutput ({})", pMonitor->m_name), pMonitor->m_self.lock()));
auto p = PROTO::outputs.emplace(pMonitor->m_name,
makeShared<CWLOutputProtocol>(&wl_output_interface, 4, std::format("WLOutput ({})", pMonitor->m_name), pMonitor->m_self.lock()));
p.first->second->m_self = p.first->second;
}
if (PROTO::colorManagement && g_pCompositor->shouldChangePreferredImageDescription()) {

View file

@ -105,8 +105,10 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
return;
}
m_sessionLock->sendDeniedTimer = makeShared<CEventLoopTimer>(
// Within this arbitrary amount of time, a session-lock client is expected to create and commit a lock surface for each output. If the client fails to do that, it will be denied.
m_sessionLock->sendLockedTimer = makeShared<CEventLoopTimer>(
// Clients get sent the "locked" event after they submitted a lock frame for each output.
// If they fail to do this, we send the "locked" event after a fixed amount of time here.
// Previously we sent denied after this timeout, but that forcefully makes the client exit and the protocol doesn't require that anyways.
std::chrono::seconds(5),
[](auto, auto) {
if (!g_pSessionLockManager || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied())
@ -115,29 +117,22 @@ void CSessionLockManager::onNewSessionLock(SP<CSessionLock> pLock) {
if (!g_pSessionLockManager->m_sessionLock || !g_pSessionLockManager->m_sessionLock->lock)
return;
if (g_pCompositor->m_unsafeState || !g_pCompositor->m_aqBackend->hasSession() || !g_pCompositor->m_aqBackend->session->active) {
// Because the session is inactive, there is a good reason for why the client did't manage to render to all outputs.
// We send locked, although this could lead to imperfect frames when we start to render again.
g_pSessionLockManager->m_sessionLock->lock->sendLocked();
g_pSessionLockManager->m_sessionLock->hasSentLocked = true;
return;
}
LOGM(Log::WARN, "Kicking lockscreen client, because it failed to render to all outputs within 5 seconds");
g_pSessionLockManager->m_sessionLock->lock->sendDenied();
g_pSessionLockManager->m_sessionLock->hasSentDenied = true;
LOGM(Log::WARN,
"Sending locked after a 5 second timeout. This happens when we failed to render a lock frame from the client for every output. Lockdead frames may be shown.");
g_pSessionLockManager->m_sessionLock->lock->sendLocked();
g_pSessionLockManager->m_sessionLock->hasSentLocked = true;
},
nullptr);
g_pEventLoopManager->addTimer(m_sessionLock->sendDeniedTimer);
g_pEventLoopManager->addTimer(m_sessionLock->sendLockedTimer);
}
void CSessionLockManager::removeSendDeniedTimer() {
if (!m_sessionLock || !m_sessionLock->sendDeniedTimer)
void CSessionLockManager::removeSendLockedTimer() {
if (!m_sessionLock || !m_sessionLock->sendLockedTimer)
return;
g_pEventLoopManager->removeTimer(m_sessionLock->sendDeniedTimer);
m_sessionLock->sendDeniedTimer.reset();
g_pEventLoopManager->removeTimer(m_sessionLock->sendLockedTimer);
m_sessionLock->sendLockedTimer.reset();
}
bool CSessionLockManager::isSessionLocked() {
@ -169,7 +164,7 @@ void CSessionLockManager::onLockscreenRenderedOnMonitor(uint64_t id) {
std::ranges::all_of(g_pCompositor->m_monitors, [this](auto m) { return !m->m_enabled || !m->m_dpmsStatus || m_sessionLock->lockedMonitors.contains(m->m_id); });
if (LOCKED && m_sessionLock->lock->good()) {
removeSendDeniedTimer();
removeSendLockedTimer();
m_sessionLock->lock->sendLocked();
m_sessionLock->hasSentLocked = true;
}

View file

@ -31,7 +31,7 @@ struct SSessionLockSurface {
struct SSessionLock {
WP<CSessionLock> lock;
CTimer lockTimer;
SP<CEventLoopTimer> sendDeniedTimer;
SP<CEventLoopTimer> sendLockedTimer;
std::vector<SP<SSessionLockSurface>> vSessionLockSurfaces;
@ -73,7 +73,7 @@ class CSessionLockManager {
} m_listeners;
void onNewSessionLock(SP<CSessionLock> pWlrLock);
void removeSendDeniedTimer();
void removeSendLockedTimer();
};
inline UP<CSessionLockManager> g_pSessionLockManager;

View file

@ -42,7 +42,7 @@ static void updateVariable(CAnimatedVariable<VarType>& av, const float POINTY, b
av.value() = av.begun() + DELTA * POINTY;
}
static void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp) {
static void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp = false) {
if (warp || av.value() == av.goal()) {
av.warp(true, false);
return;
@ -139,15 +139,12 @@ static void handleUpdate(CAnimatedVariable<VarType>& av, bool warp) {
animationsDisabled = animationsDisabled || ls->m_ruleApplicator->noanim().valueOrDefault();
}
const auto SPENT = av.getPercent();
const auto PBEZIER = g_pAnimationManager->getBezier(av.getBezierName());
const auto POINTY = PBEZIER->getYForPoint(SPENT);
const bool WARP = animationsDisabled || SPENT >= 1.f;
const auto STEP = av.getCurveStep();
if constexpr (std::same_as<VarType, CHyprColor>)
updateColorVariable(av, POINTY, WARP);
updateColorVariable(av, STEP.value, STEP.finished || animationsDisabled);
else
updateVariable<VarType>(av, POINTY, WARP);
updateVariable<VarType>(av, STEP.value, STEP.finished || animationsDisabled);
av.onUpdate();
}

View file

@ -18,25 +18,26 @@
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::String;
using namespace Desktop::View;
void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType type, bool force) {
const bool CLOSE = type == ANIMATION_TYPE_OUT;
if (CLOSE)
*pWindow->m_alpha = 0.F;
*pWindow->alpha(WINDOW_ALPHA_FADE) = 0.F;
else {
pWindow->m_alpha->setValueAndWarp(0.F);
*pWindow->m_alpha = 1.F;
pWindow->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(0.F);
*pWindow->alpha(WINDOW_ALPHA_FADE) = 1.F;
}
if (!CLOSE) {
pWindow->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsIn"));
pWindow->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsIn"));
pWindow->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeIn"));
pWindow->alpha(WINDOW_ALPHA_FADE)->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeIn"));
} else {
pWindow->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsOut"));
pWindow->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsOut"));
pWindow->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeOut"));
pWindow->alpha(WINDOW_ALPHA_FADE)->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeOut"));
}
std::string ANIMSTYLE = pWindow->m_realPosition->getStyle();
@ -466,20 +467,17 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim
const auto FULLSCREEN = type == ANIMATION_TYPE_IN;
const auto FSWINDOW = ws->getFullscreenWindow();
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == ws) {
w->updateFullscreenInputState();
if (w->m_fadingOut || w->m_pinned || w->isFullscreen())
continue;
if (!FULLSCREEN)
*w->m_alpha = 1.F;
*w->alpha(WINDOW_ALPHA_FULLSCREEN) = 1.F;
else if (!w->isFullscreen()) {
const bool CREATED_OVER_FS = w->m_createdOverFullscreen;
const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w);
*w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f;
*w->alpha(WINDOW_ALPHA_FULLSCREEN) = w->isAllowedOverFullscreen() ? 1.f : 0.f;
}
}
}
@ -498,7 +496,8 @@ void CDesktopAnimationManager::setFullscreenFloatingFade(PHLWINDOW pWindow, floa
if (pWindow->m_fadingOut || !pWindow->m_isFloating)
return;
*pWindow->m_alpha = fade;
*pWindow->alpha(WINDOW_ALPHA_FULLSCREEN) = fade;
pWindow->updateFullscreenInputState();
}
void CDesktopAnimationManager::overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude) {
@ -510,7 +509,8 @@ void CDesktopAnimationManager::overrideFullscreenFadeAmount(PHLWORKSPACE ws, flo
if (w->m_fadingOut || w->m_pinned || w->isFullscreen())
continue;
*w->m_alpha = fade;
*w->alpha(WINDOW_ALPHA_FULLSCREEN) = fade;
w->updateFullscreenInputState();
}
}

View file

@ -16,6 +16,17 @@ using namespace Hyprutils::OS;
#define TIMESPEC_NSEC_PER_SEC 1000000000L
static uint64_t LAST_DO_LATER_SEQ = 1;
SEventLoopDoLaterLock::SEventLoopDoLaterLock(uint64_t seq_) : seq(seq_) {
;
}
SEventLoopDoLaterLock::~SEventLoopDoLaterLock() {
if (g_pEventLoopManager && seq > 0)
g_pEventLoopManager->removeDoLater(seq);
}
CEventLoopManager::CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop) {
m_timers.timerfd = CFileDescriptor{timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)};
m_wayland.loop = wlEventLoop;
@ -201,11 +212,13 @@ void CEventLoopManager::nudgeTimers() {
timerfd_settime(m_timers.timerfd.get(), TFD_TIMER_ABSTIME, &ts, nullptr);
}
void CEventLoopManager::doLater(const std::function<void()>& fn) {
m_idle.fns.emplace_back(fn);
uint64_t CEventLoopManager::doLater(const std::function<void()>& fn) {
const uint64_t NEW_SEQ = ++LAST_DO_LATER_SEQ;
m_idle.fns.emplace_back(std::make_pair<>(NEW_SEQ, fn));
if (m_idle.eventSource)
return;
return NEW_SEQ;
m_idle.eventSource = wl_event_loop_add_idle(
m_wayland.loop,
@ -215,11 +228,26 @@ void CEventLoopManager::doLater(const std::function<void()>& fn) {
IDLE->fns.clear();
IDLE->eventSource = nullptr;
for (auto& f : fns) {
if (f)
f();
if (f.second)
f.second();
}
},
&m_idle);
return NEW_SEQ;
}
void CEventLoopManager::removeDoLater(uint64_t seq) {
std::erase_if(m_idle.fns, [&seq](const auto& e) { return e.first == seq; });
if (m_idle.fns.empty() && m_idle.eventSource) {
wl_event_source_remove(m_idle.eventSource);
m_idle.eventSource = nullptr;
}
}
UP<SEventLoopDoLaterLock> CEventLoopManager::doLaterLock(const std::function<void()>& fn) {
return makeUnique<SEventLoopDoLaterLock>(doLater(fn));
}
void CEventLoopManager::doOnReadable(CFileDescriptor fd, std::function<void()>&& fn) {

View file

@ -14,6 +14,13 @@ namespace Aquamarine {
struct SPollFD;
};
struct SEventLoopDoLaterLock {
SEventLoopDoLaterLock(uint64_t seq);
~SEventLoopDoLaterLock();
uint64_t seq = 0;
};
class CEventLoopManager {
public:
CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop);
@ -30,12 +37,16 @@ class CEventLoopManager {
// schedules a recalc of the timers
void scheduleRecalc();
// schedules a function to run later, aka in a wayland idle event.
void doLater(const std::function<void()>& fn);
// schedules a function to run later, aka in a wayland idle event. Returns a sequence which can be used to remove it.
uint64_t doLater(const std::function<void()>& fn);
void removeDoLater(uint64_t seq);
// automatically cleaned up doLater instance
[[nodiscard]] UP<SEventLoopDoLaterLock> doLaterLock(const std::function<void()>& fn);
struct SIdleData {
wl_event_source* eventSource = nullptr;
std::vector<std::function<void()>> fns;
wl_event_source* eventSource = nullptr;
std::vector<std::pair<uint64_t, std::function<void()>>> fns;
};
struct SReadableWaiter {

View file

@ -265,31 +265,56 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE);
// constraints
if (!overridePos.has_value() && !g_pSeatManager->m_mouse.expired() && isConstrained()) {
const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());
const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr;
if (CONSTRAINT) {
if (CONSTRAINT->isLocked()) {
const auto HINT = CONSTRAINT->logicPositionHint();
g_pCompositor->warpCursorTo(HINT, true);
} else {
const auto RG = CONSTRAINT->logicConstraintRegion();
const auto CLOSEST = RG.closestPoint(mouseCoords);
const auto BOX = SURF->getSurfaceBoxGlobal();
const auto WINDOW = Desktop::View::CWindow::fromView(SURF->view());
const auto CLOSESTLOCAL = (CLOSEST - (BOX.has_value() ? BOX->pos() : Vector2D{})) * (WINDOW ? WINDOW->m_X11SurfaceScaledBy : 1.0);
g_pCompositor->warpCursorTo(CLOSEST, true);
g_pSeatManager->sendPointerMotion(time, CLOSESTLOCAL);
PROTO::relativePointer->sendRelativeMotion(sc<uint64_t>(time) * 1000, {}, {});
}
auto confineToRegion = [&](const CRegion& rg, SP<Desktop::View::CWLSurface> surf) {
if (!surf)
return;
} else
Log::logger->log(Log::ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", rc<uintptr_t>(SURF.get()),
rc<uintptr_t>(CONSTRAINT.get()));
const auto CLOSEST = rg.closestPoint(mouseCoords);
const auto BOX = surf->getSurfaceBoxGlobal();
const auto WINDOW = Desktop::View::CWindow::fromView(surf->view());
const auto CLOSESTLOCAL = (CLOSEST - (BOX.has_value() ? BOX->pos() : Vector2D{})) * (WINDOW ? WINDOW->m_X11SurfaceScaledBy : 1.0);
if (g_pSeatManager->m_state.pointerFocus != surf->resource())
g_pSeatManager->setPointerFocus(surf->resource(), CLOSESTLOCAL);
g_pCompositor->warpCursorTo(CLOSEST, true);
g_pSeatManager->sendPointerMotion(time, CLOSESTLOCAL);
PROTO::relativePointer->sendRelativeMotion(sc<uint64_t>(time) * 1000, {}, {});
};
if (!g_pSeatManager->m_mouse.expired()) {
const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface());
if (isConstrained()) {
const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr;
if (CONSTRAINT) {
if (CONSTRAINT->isLocked()) {
const auto HINT = CONSTRAINT->logicPositionHint();
g_pCompositor->warpCursorTo(HINT, true);
} else {
confineToRegion(CONSTRAINT->logicConstraintRegion(), SURF);
}
return;
} else {
Log::logger->log(Log::ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", rc<uintptr_t>(SURF.get()),
rc<uintptr_t>(CONSTRAINT.get()));
}
} else {
const auto WINDOW = SURF ? Desktop::View::CWindow::fromView(SURF->view()) : nullptr;
if (WINDOW) {
if (WINDOW->m_ruleApplicator->confinePointer().valueOrDefault()) {
const auto BOX = SURF->getSurfaceBoxGlobal();
if (BOX.has_value()) {
CRegion rg;
rg.set(*BOX);
confineToRegion(rg, SURF);
}
return;
}
}
}
}
if (PMONITOR != Desktop::focusState()->monitor() && (*PMOUSEFOCUSMON || refocus) && m_forcedFocus.expired())
@ -447,7 +472,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
const auto& PWINDOWIDEAL = getWindowIdeal();
if (PWINDOWIDEAL &&
((PWINDOWIDEAL->m_isFloating && (PWINDOWIDEAL->m_createdOverFullscreen || PWINDOWIDEAL->m_pinned)) /* floating over fullscreen or pinned */
((PWINDOWIDEAL->m_isFloating && PWINDOWIDEAL->isAllowedOverFullscreen()) /* floating over fullscreen or pinned */
|| (PMONITOR->m_activeSpecialWorkspace == PWINDOWIDEAL->m_workspace) /* on an open special workspace */))
pFoundWindow = PWINDOWIDEAL;
@ -485,7 +510,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st
if (pFoundWindow != PWINDOWIDEAL)
pFoundWindow = PWINDOWIDEAL;
if (!(pFoundWindow && (pFoundWindow->m_isFloating && (pFoundWindow->m_createdOverFullscreen || pFoundWindow->m_pinned))))
if (!(pFoundWindow && (pFoundWindow->m_isFloating && pFoundWindow->isAllowedOverFullscreen())))
pFoundWindow = PWORKSPACE->getFullscreenWindow();
}
}
@ -1142,6 +1167,10 @@ void CInputManager::applyConfigToKeyboard(SP<IKeyboard> pKeyboard) {
const auto ENABLED = HASCONFIG && Config::mgr()->deviceConfigExplicitlySet(devname, "enabled") ? Config::mgr()->getDeviceInt(devname, "enabled") : true;
const auto ALLOWBINDS = HASCONFIG && Config::mgr()->deviceConfigExplicitlySet(devname, "keybinds") ? Config::mgr()->getDeviceInt(devname, "keybinds") : true;
for (const auto& tagString : CVarList2(Config::mgr()->getDeviceString(devname, "tags"))) {
pKeyboard->m_deviceTags.emplace(std::string_view(tagString));
}
pKeyboard->m_enabled = ENABLED;
pKeyboard->m_resolveBindsBySym = RESOLVEBINDSBYSYM;
pKeyboard->m_allowBinds = ALLOWBINDS;
@ -1268,6 +1297,10 @@ void CInputManager::setPointerConfigs() {
g_pPointerManager->detachPointer(m);
m->m_connected = false;
}
for (const auto tagString : CVarList2(Config::mgr()->getDeviceString(devname, "tags"))) {
m->m_deviceTags.emplace(std::string_view(tagString));
}
}
if (Config::mgr()->deviceConfigExplicitlySet(devname, "scroll_factor"))
@ -1669,7 +1702,8 @@ bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) {
foundSurface = nullptr;
}
if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace && Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) {
if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_monitor == pMonitor && Desktop::focusState()->window()->m_workspace &&
Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) {
// then the last focused window if we're on the same workspace as it
const auto PLASTWINDOW = Desktop::focusState()->window();
Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM);

View file

@ -9,6 +9,8 @@
#include "../../../../desktop/state/FocusState.hpp"
#include "../../../../layout/target/Target.hpp"
using namespace Desktop::View;
constexpr const float MAX_DISTANCE = 200.F;
static std::vector<SP<CEventLoopTimer>> trackpadCloseTimers;
@ -33,18 +35,18 @@ void CCloseTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin&
if (!m_window)
return;
m_alphaFrom = m_window->m_alpha->goal();
m_alphaFrom = m_window->alphaGoal(WINDOW_ALPHA_FADE);
m_posFrom = m_window->m_realPosition->goal();
m_sizeFrom = m_window->m_realSize->goal();
g_pDesktopAnimationManager->startAnimation(m_window.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT, true);
*m_window->m_alpha = 0.f;
*m_window->alpha(WINDOW_ALPHA_FADE) = 0.f;
m_alphaTo = m_window->m_alpha->goal();
m_alphaTo = m_window->alphaGoal(WINDOW_ALPHA_FADE);
m_posTo = m_window->m_realPosition->goal();
m_sizeTo = m_window->m_realSize->goal();
m_window->m_alpha->setValueAndWarp(m_alphaFrom);
m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(m_alphaFrom);
m_window->m_realPosition->setValueAndWarp(m_posFrom);
m_window->m_realSize->setValueAndWarp(m_sizeFrom);
@ -61,7 +63,7 @@ void CCloseTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdat
const auto FADEPERCENT = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);
m_window->m_alpha->setValueAndWarp(lerpVal(m_alphaFrom, m_alphaTo, FADEPERCENT));
m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(lerpVal(m_alphaFrom, m_alphaTo, FADEPERCENT));
m_window->m_realPosition->setValueAndWarp(lerpVal(m_posFrom, m_posTo, FADEPERCENT));
m_window->m_realSize->setValueAndWarp(lerpVal(m_sizeFrom, m_sizeTo, FADEPERCENT));
@ -81,20 +83,20 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e)
if (COMPLETION < 0.2F) {
// revert the animation
g_pHyprRenderer->damageWindow(m_window.lock());
*m_window->m_alpha = m_alphaFrom;
*m_window->m_realPosition = m_posFrom;
*m_window->m_realSize = m_sizeFrom;
*m_window->alpha(WINDOW_ALPHA_FADE) = m_alphaFrom;
*m_window->m_realPosition = m_posFrom;
*m_window->m_realSize = m_sizeFrom;
return;
}
// commence. Close the window and restore our current state to avoid a harsh anim
const auto CURRENT_ALPHA = m_window->m_alpha->value();
const auto CURRENT_ALPHA = m_window->alphaValue(WINDOW_ALPHA_FADE);
const auto CURRENT_POS = m_window->m_realPosition->value();
const auto CURRENT_SIZE = m_window->m_realSize->value();
Desktop::focusState()->window()->sendClose();
m_window->m_alpha->setValueAndWarp(CURRENT_ALPHA);
m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(CURRENT_ALPHA);
m_window->m_realPosition->setValueAndWarp(CURRENT_POS);
m_window->m_realSize->setValueAndWarp(CURRENT_SIZE);
@ -136,7 +138,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e)
window->layoutTarget()->recalc();
window->updateDecorationValues();
window->sendWindowSize(true);
*window->m_alpha = 1.F;
*window->alpha(WINDOW_ALPHA_FADE) = 1.F;
},
nullptr);
trackpadCloseTimers.emplace_back(timer);

View file

@ -2,19 +2,40 @@
#include "../../../../Compositor.hpp"
#include "../../../../helpers/Monitor.hpp"
#include "../../../../managers/input/InputManager.hpp"
#include <hyprutils/string/Numeric.hpp>
CCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) {
try {
m_zoomValue = std::stof(first);
} catch (...) { ; }
if (const auto n = Hyprutils::String::strToNumber<float>(first); n)
m_zoomValue = n.value();
if (second == "mult")
m_mode = MODE_MULT;
else if (second == "live")
m_mode = MODE_LIVE;
}
void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {
ITrackpadGesture::begin(e);
if (m_mode == MODE_LIVE) {
if (!e.pinch)
return;
m_monitor = g_pCompositor->getMonitorFromCursor();
if (!m_monitor)
return;
const auto PMONITOR = m_monitor.lock();
if (!PMONITOR)
return;
m_zoomBegin = std::clamp(PMONITOR->m_cursorZoom->value(), 1.0F, 100.0F);
PMONITOR->m_cursorZoom->setValueAndWarp(m_zoomBegin);
PMONITOR->m_zoomController.pinAnchor(g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position);
return;
}
if (m_mode == MODE_TOGGLE)
m_zoomed = !m_zoomed;
@ -25,9 +46,35 @@ void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureB
*m->m_cursorZoom = m_zoomed ? m_zoomValue : *PZOOMFACTOR;
break;
case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break;
case MODE_LIVE: break;
}
}
}
void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {}
void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {}
void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {
if (m_mode != MODE_LIVE || !m_monitor || !e.pinch)
return;
const auto PMONITOR = m_monitor.lock();
if (!PMONITOR)
return;
auto zoom = std::clamp(m_zoomBegin * static_cast<float>(e.pinch->scale), 1.0F, 100.0F);
if (zoom < 1.05F)
zoom = 1.0F;
PMONITOR->m_cursorZoom->setValueAndWarp(zoom);
}
void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {
if (m_mode != MODE_LIVE || !m_monitor)
return;
const auto PMONITOR = m_monitor.lock();
if (!PMONITOR)
return;
PMONITOR->m_zoomController.clearAnchor();
m_monitor.reset();
}

View file

@ -2,6 +2,8 @@
#include "ITrackpadGesture.hpp"
#include "../../../../desktop/DesktopTypes.hpp"
class CCursorZoomTrackpadGesture : public ITrackpadGesture {
public:
CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode);
@ -14,10 +16,13 @@ class CCursorZoomTrackpadGesture : public ITrackpadGesture {
private:
float m_zoomValue = 1.0;
inline static bool m_zoomed = false;
PHLMONITORREF m_monitor;
float m_zoomBegin = 1.0;
enum eMode : uint8_t {
MODE_TOGGLE = 0,
MODE_MULT,
MODE_LIVE,
};
eMode m_mode = MODE_TOGGLE;

View file

@ -18,6 +18,7 @@
using namespace Hyprgraphics::Egl;
using namespace Screenshare;
using namespace Desktop::View;
CScreenshareFrame::CScreenshareFrame(WP<CScreenshareSession> session, bool overlayCursor, bool isFirst) :
m_session(session), m_bufferSize(m_session->bufferSize()), m_overlayCursor(overlayCursor), m_isFirst(isFirst) {
@ -259,7 +260,7 @@ void CScreenshareFrame::renderMonitor() {
const auto PWORKSPACE = w->m_workspace;
if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f)
if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->alphaValue(WINDOW_ALPHA_FADE) * w->alphaValue(WINDOW_ALPHA_FULLSCREEN) != 0.f)
continue;
const auto renderOffset = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{};

View file

@ -140,16 +140,18 @@ WP<CScreenshareSession> CScreenshareManager::getManagedSession(eScreenshareType
m_sessions.emplace_back(session);
it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique<SManagedSession>(std::move(session)));
auto& managed = *it;
managed->stoppedListener = managed->m_session->m_events.stopped.listen([managed = WP<SManagedSession>(managed)]() {
if (!managed)
return;
const auto& session = managed->m_session;
std::erase_if(Screenshare::mgr()->m_managedSessions, [&session](const auto& s) { return s && s->m_session == session; });
});
}
auto& session = *it;
session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() {
if (!session.expired())
std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); });
});
return session->m_session;
return (*it)->m_session;
}
bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) {

View file

@ -56,9 +56,9 @@ void CScreenshareSession::stop() {
if (m_stopped)
return;
m_stopped = true;
m_events.stopped.emit();
screenshareEvents(false);
m_events.stopped.emit();
}
bool CScreenshareSession::isActive() {
@ -80,8 +80,9 @@ void CScreenshareSession::init() {
if (g_pEventLoopManager)
g_pEventLoopManager->addTimer(m_shareStopTimer);
// scale capture box since it's in logical coords
m_captureBox.scale(monitor()->m_scale);
// scale capture box since it's in logical coords; round to integer pixel
// dims so m_bufferSize matches the int32 size we send to the client
m_captureBox.scale(monitor()->m_scale).round();
m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); });
m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() {

View file

@ -4,6 +4,7 @@
#include "../Compositor.hpp"
#include "../managers/SeatManager.hpp"
#include "../managers/ANRManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../helpers/Monitor.hpp"
#include "core/Seat.hpp"
#include "core/Compositor.hpp"
@ -286,7 +287,7 @@ bool CXDGToplevelResource::anyChildModal() {
uint32_t CXDGToplevelResource::setSize(const Vector2D& size) {
m_pendingApply.size = size;
applyState();
scheduleStateApplication();
return m_owner->scheduleConfigure();
}
@ -300,7 +301,9 @@ uint32_t CXDGToplevelResource::setMaximized(bool maximized) {
m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_MAXIMIZED);
else if (!maximized && set)
std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_MAXIMIZED);
applyState();
scheduleStateApplication();
return m_owner->scheduleConfigure();
}
@ -314,7 +317,9 @@ uint32_t CXDGToplevelResource::setFullscreen(bool fullscreen) {
m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_FULLSCREEN);
else if (!fullscreen && set)
std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_FULLSCREEN);
applyState();
scheduleStateApplication();
return m_owner->scheduleConfigure();
}
@ -328,7 +333,9 @@ uint32_t CXDGToplevelResource::setActive(bool active) {
m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_ACTIVATED);
else if (!active && set)
std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_ACTIVATED);
applyState();
scheduleStateApplication();
return m_owner->scheduleConfigure();
}
@ -345,22 +352,32 @@ uint32_t CXDGToplevelResource::setSuspeneded(bool sus) {
m_pendingApply.states.push_back(XDG_TOPLEVEL_STATE_SUSPENDED);
else if (!sus && set)
std::erase(m_pendingApply.states, XDG_TOPLEVEL_STATE_SUSPENDED);
applyState();
scheduleStateApplication();
return m_owner->scheduleConfigure();
}
void CXDGToplevelResource::applyState() {
wl_array arr;
wl_array_init(&arr);
void CXDGToplevelResource::scheduleStateApplication() {
if (!m_pendingApply.states.empty()) {
wl_array_add(&arr, m_pendingApply.states.size() * sizeof(int));
memcpy(arr.data, m_pendingApply.states.data(), m_pendingApply.states.size() * sizeof(int));
}
if (m_stateUpdate)
return;
m_resource->sendConfigure(m_pendingApply.size.x, m_pendingApply.size.y, &arr);
m_stateUpdate = g_pEventLoopManager->doLaterLock([this] {
wl_array arr;
wl_array_init(&arr);
wl_array_release(&arr);
if (!m_pendingApply.states.empty()) {
wl_array_add(&arr, m_pendingApply.states.size() * sizeof(int));
memcpy(arr.data, m_pendingApply.states.data(), m_pendingApply.states.size() * sizeof(int));
}
m_resource->sendConfigure(m_pendingApply.size.x, m_pendingApply.size.y, &arr);
wl_array_release(&arr);
m_stateUpdate.reset();
});
}
void CXDGToplevelResource::close() {
@ -516,8 +533,6 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP<CXdgSurface> resource_, SP<CXDGWMBas
CXDGSurfaceResource::~CXDGSurfaceResource() {
m_events.destroy.emit();
if (m_configureSource)
wl_event_source_remove(m_configureSource);
if (m_surface)
m_surface->resetRole();
}
@ -531,22 +546,19 @@ SP<CXDGSurfaceResource> CXDGSurfaceResource::fromResource(wl_resource* res) {
return data ? data->m_self.lock() : nullptr;
}
static void onConfigure(void* data) {
sc<CXDGSurfaceResource*>(data)->configure();
}
uint32_t CXDGSurfaceResource::scheduleConfigure() {
if (m_configureSource)
if (m_stateUpdate)
return m_scheduledSerial;
m_configureSource = wl_event_loop_add_idle(g_pCompositor->m_wlEventLoop, onConfigure, this);
m_stateUpdate = g_pEventLoopManager->doLaterLock([this] { configure(); });
m_scheduledSerial = wl_display_next_serial(g_pCompositor->m_wlDisplay);
return m_scheduledSerial;
}
void CXDGSurfaceResource::configure() {
m_configureSource = nullptr;
m_stateUpdate.reset();
m_resource->sendConfigure(m_scheduledSerial);
}

View file

@ -18,6 +18,7 @@ class CXDGPopupResource;
class CSeatGrab;
class CWLSurfaceResource;
class CXDGDialogV1Resource;
struct SEventLoopDoLaterLock;
struct SXDGPositionerState {
Vector2D requestedSize;
@ -149,8 +150,11 @@ class CXDGToplevelResource {
std::vector<WP<CXDGToplevelResource>> m_children;
private:
SP<CXdgToplevel> m_resource;
void applyState();
SP<CXdgToplevel> m_resource;
UP<SEventLoopDoLaterLock> m_stateUpdate;
void scheduleStateApplication();
};
class CXDGSurfaceRole : public ISurfaceRole {
@ -202,12 +206,12 @@ class CXDGSurfaceResource {
void configure();
private:
SP<CXdgSurface> m_resource;
SP<CXdgSurface> m_resource;
uint32_t m_lastConfigureSerial = 0;
uint32_t m_scheduledSerial = 0;
UP<SEventLoopDoLaterLock> m_stateUpdate;
wl_event_source* m_configureSource = nullptr;
uint32_t m_lastConfigureSerial = 0;
uint32_t m_scheduledSerial = 0;
//
std::vector<WP<CXDGPopupResource>> m_popups;

View file

@ -59,6 +59,7 @@
using namespace Hyprutils::OS;
using namespace NColorManagement;
using namespace Desktop::View;
using namespace Render;
using namespace Render::GL;
@ -1867,7 +1868,8 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) {
const auto PSURFACE = pWindow->wlSurface()->resource();
const auto PWORKSPACE = pWindow->m_workspace;
const float A = pWindow->m_alpha->value() * pWindow->m_activeInactiveAlpha->value() * PWORKSPACE->m_alpha->value();
const float A = pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) * pWindow->alphaValue(WINDOW_ALPHA_LAYOUT) *
pWindow->alphaValue(WINDOW_ALPHA_ACTIVE) * PWORKSPACE->m_alpha->value();
if (A >= 1.f) {
// if (PSURFACE->opaque)
@ -1888,7 +1890,7 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) {
bool hasWindows = false;
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == pMonitor->m_activeWorkspace && !w->isHidden() && w->m_isMapped && (!w->m_isFloating || *PBLURXRAY)) {
if (w->m_workspace == pMonitor->m_activeWorkspace && w->visible() && w->m_isMapped && (!w->m_isFloating || *PBLURXRAY)) {
// check if window is valid
if (!windowShouldBeBlurred(w))

View file

@ -23,7 +23,6 @@
#include "../protocols/core/Compositor.hpp"
#include "../protocols/DRMSyncobj.hpp"
#include "../protocols/LinuxDMABUF.hpp"
#include "../helpers/sync/SyncTimeline.hpp"
#include "../errorOverlay/Overlay.hpp"
#include "../debug/Overlay.hpp"
#include "../notification/NotificationOverlay.hpp"
@ -36,7 +35,6 @@
#include "../helpers/MainLoopExecutor.hpp"
#include "../helpers/Monitor.hpp"
#include "macros.hpp"
#include "../managers/screenshare/ScreenshareManager.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/ClearPassElement.hpp"
#include "pass/RectPassElement.hpp"
@ -45,7 +43,6 @@
#include "../debug/log/Logger.hpp"
#include "../protocols/ColorManagement.hpp"
#include "../protocols/types/ContentType.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "AsyncResourceGatherer.hpp"
#include "ElementRenderer.hpp"
#include "Framebuffer.hpp"
@ -68,6 +65,7 @@ using namespace Hyprutils::Utils;
using namespace Hyprutils::OS;
using enum NContentType::eContentType;
using namespace NColorManagement;
using namespace Desktop::View;
using namespace Render;
extern "C" {
@ -234,8 +232,8 @@ bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) {
return true;
// if the window is being moved to a workspace that is not invisible, and the alpha is > 0.F, render it.
if (pWindow->m_monitorMovedFrom != -1 && pWindow->m_movingToWorkspaceAlpha->isBeingAnimated() && pWindow->m_movingToWorkspaceAlpha->value() > 0.F && pWindow->m_workspace &&
!pWindow->m_workspace->isVisible())
if (pWindow->m_monitorMovedFrom != -1 && pWindow->alpha(WINDOW_ALPHA_MOVE_TO_WORKSPACE)->isBeingAnimated() && pWindow->alphaValue(WINDOW_ALPHA_MOVE_TO_WORKSPACE) > 0.F &&
pWindow->m_workspace && !pWindow->m_workspace->isVisible())
return true;
const auto PWINDOWWORKSPACE = pWindow->m_workspace;
@ -244,7 +242,8 @@ bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) {
return true;
// if hidden behind fullscreen
if (PWINDOWWORKSPACE->m_hasFullscreenWindow && !pWindow->isFullscreen() && (!pWindow->m_isFloating || !pWindow->m_createdOverFullscreen) && pWindow->m_alpha->value() == 0)
if (PWINDOWWORKSPACE->m_hasFullscreenWindow && !pWindow->isAllowedOverFullscreen() &&
pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) == 0)
return false;
if (!PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOWWORKSPACE->m_alpha->isBeingAnimated() && !PWINDOWWORKSPACE->isVisible())
@ -325,7 +324,7 @@ void IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR
if (!shouldRenderWindow(w, pMonitor))
continue;
if (w->m_alpha->value() == 0.f)
if (w->alphaValue(WINDOW_ALPHA_FADE) * w->alphaValue(WINDOW_ALPHA_FULLSCREEN) == 0.f)
continue;
if (w->isFullscreen())
@ -356,6 +355,9 @@ void IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR
if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)
continue; // special on another are rendered as a part of the base pass
if (w->isFadingOutUnderFullscreen())
continue; // render these over fullscreen so the fade-out is visible
renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);
}
@ -394,8 +396,8 @@ void IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR
// then render windows over fullscreen.
for (auto const& w : g_pCompositor->m_windows) {
const bool shouldSkipWindow = w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || (!w->m_createdOverFullscreen && !w->m_pinned) ||
(!w->m_isMapped && !w->m_fadingOut) || w->isFullscreen();
const bool shouldSkipWindow =
w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || !w->shouldRenderOverFullscreen() || (!w->m_isMapped && !w->m_fadingOut) || w->isFullscreen();
if (shouldSkipWindow)
continue;
@ -527,6 +529,9 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
if (pWindow->isHidden() && !standalone)
return;
if (!standalone && pWindow->effectiveAlpha() == 0.F && !pWindow->m_alpha.isBeingAnimated())
return;
if (pWindow->m_fadingOut) {
if (pMonitor == pWindow->m_monitor) // TODO: fix this
renderSnapshot(pWindow);
@ -569,9 +574,10 @@ void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T
renderdata.surface = pWindow->wlSurface()->resource();
renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);
renderdata.fadeAlpha = pWindow->m_alpha->value() * (pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) *
(USE_WORKSPACE_FADE_ALPHA ? pWindow->m_movingToWorkspaceAlpha->value() : 1.F) * pWindow->m_movingFromWorkspaceAlpha->value();
renderdata.alpha = pWindow->m_activeInactiveAlpha->value();
renderdata.fadeAlpha = pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) * pWindow->alphaValue(WINDOW_ALPHA_LAYOUT) *
(pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) *
(USE_WORKSPACE_FADE_ALPHA ? pWindow->alphaValue(WINDOW_ALPHA_MOVE_TO_WORKSPACE) : 1.F) * pWindow->alphaValue(WINDOW_ALPHA_MOVE_FROM_WORKSPACE);
renderdata.alpha = pWindow->alphaValue(WINDOW_ALPHA_ACTIVE);
renderdata.decorate = decorate && !pWindow->m_X11DoesntWantBorders && !pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);
renderdata.rounding = standalone || renderdata.dontRound ? 0 : pWindow->rounding() * pMonitor->m_scale;
renderdata.roundingPower = standalone || renderdata.dontRound ? 2.0f : pWindow->roundingPower();
@ -1228,9 +1234,58 @@ SP<ITexture> IHyprRenderer::getBackground(PHLMONITOR pMonitor) {
Log::logger->log(Log::DEBUG, "Creating a texture for BGTex");
SP<ITexture> backgroundTexture = createTexture(m_backgroundResource->m_asset.cairoSurface->cairo());
if (!backgroundTexture->ok())
if (!backgroundTexture || !backgroundTexture->ok())
return nullptr;
Log::logger->log(Log::DEBUG, "Background created for monitor {}", pMonitor->m_name);
Log::logger->log(Log::DEBUG, "BGTex created for monitor {}", pMonitor->m_name);
const int monW = (int)std::round(pMonitor->m_transformedSize.x);
const int monH = (int)std::round(pMonitor->m_transformedSize.y);
const int origW = backgroundTexture->m_size.x;
const int origH = backgroundTexture->m_size.y;
if (monW > 0 && monH > 0) {
const double scaleX = (double)monW / origW;
const double scaleY = (double)monH / origH;
const double scale = std::max(scaleX, scaleY);
// scale the background if it's larger than the monitor
if (scale < 1.0) {
auto fb = createFB("BGTex scale");
fb->alloc(monW, monH);
auto guard = bindTempFB(fb);
const auto oldProjType = m_renderData.projectionType;
const auto oldFbSize = m_renderData.fbSize;
const auto oldTransformDmg = m_renderData.transformDamage;
m_renderData.fbSize = Vector2D{monW, monH};
setProjectionType(RPT_EXPORT);
m_renderData.transformDamage = false;
setViewport(0, 0, monW, monH);
draw(CClearPassElement::SClearData{{0.F, 0.F, 0.F, 0.F}});
const double texW = origW * scale;
const double texH = origH * scale;
const double offX = (monW - texW) / 2.0;
const double offY = (monH - texH) / 2.0;
CRegion fullDamage = {0, 0, monW, monH};
draw(CTexPassElement::SRenderData{.tex = backgroundTexture, .box = CBox{offX, offY, texW, texH}, .damage = fullDamage}, fullDamage);
m_renderData.fbSize = oldFbSize;
m_renderData.transformDamage = oldTransformDmg;
setProjectionType(oldProjType);
setViewport(0, 0, (int)pMonitor->m_pixelSize.x, (int)pMonitor->m_pixelSize.y);
backgroundTexture = fb->getTexture();
Log::logger->log(Log::INFO, "BGTex scaled from {}x{} to {}x{} for monitor {}", origW, origH, monW, monH, pMonitor->m_name);
}
}
// clear the resource after we're done using it
g_pEventLoopManager->doLater([this] { m_backgroundResource.reset(); });
@ -2136,8 +2191,9 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
Event::bus()->m_events.render.stage.emit(RENDER_POST);
pMonitor->m_output->state->addDamage(frameDamage);
pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
auto presentationMode = shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC;
if (pMonitor->m_output->state->state().presentationMode != presentationMode)
pMonitor->m_output->state->setPresentationMode(presentationMode);
if (commit)
commitPendingAndDoExplicitSync(pMonitor);
@ -3145,8 +3201,9 @@ void IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) {
if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault()) {
CRectPassElement::SRectData data;
data.box = {0, 0, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y};
data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pWindow->m_alpha->value());
data.box = {0, 0, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y};
data.color =
CHyprColor(0, 0, 0, *PDIMAROUND * pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) * pWindow->alphaValue(WINDOW_ALPHA_LAYOUT));
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
@ -3156,7 +3213,8 @@ void IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) {
data.box = CBox{pWindow->m_realPosition->value(), pWindow->m_realSize->value()}.translate(-PMONITOR->m_position).scale(PMONITOR->m_scale).round();
data.color = CHyprColor{0, 0, 0, 0};
data.blur = true;
data.blurA = sqrt(pWindow->m_alpha->value()); // sqrt makes the blur fadeout more realistic.
data.blurA = sqrt(pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) *
pWindow->alphaValue(WINDOW_ALPHA_LAYOUT)); // sqrt makes the blur fadeout more realistic.
data.round = pWindow->rounding();
data.roundingPower = pWindow->roundingPower();
data.xray = pWindow->m_ruleApplicator->xray().valueOr(false);
@ -3168,7 +3226,7 @@ void IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) {
data.flipEndFrame = true;
data.tex = FBDATA->getTexture();
data.box = windowBox;
data.a = pWindow->m_alpha->value();
data.a = pWindow->alphaValue(WINDOW_ALPHA_FADE) * pWindow->alphaValue(WINDOW_ALPHA_FULLSCREEN) * pWindow->alphaValue(WINDOW_ALPHA_LAYOUT);
data.damage = fakeDamage;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));

View file

@ -431,9 +431,10 @@ bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWIND
}
bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPointer::SButtonEvent& e) {
static auto PSTACKED = CConfigValue<Config::INTEGER>("group:groupbar:stacked");
static auto POUTERGAP = CConfigValue<Config::INTEGER>("group:groupbar:gaps_out");
static auto PINNERGAP = CConfigValue<Config::INTEGER>("group:groupbar:gaps_in");
static auto PSTACKED = CConfigValue<Config::INTEGER>("group:groupbar:stacked");
static auto POUTERGAP = CConfigValue<Config::INTEGER>("group:groupbar:gaps_out");
static auto PINNERGAP = CConfigValue<Config::INTEGER>("group:groupbar:gaps_in");
static auto PMIDDLECLICKCLOSE = CConfigValue<Config::INTEGER>("group:groupbar:middle_click_close");
if (m_window->isEffectiveInternalFSMode(FSMODE_FULLSCREEN))
return true;
@ -444,6 +445,9 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo
// close window on middle click
if (e.button == 274) {
if (!*PMIDDLECLICKCLOSE)
return true;
static Vector2D pressedCursorPos;
if (e.state == WL_POINTER_BUTTON_STATE_PRESSED)

View file

@ -89,7 +89,7 @@ TEST(ConfigLuaValueTypes, boolBadType) {
CLuaConfigBool value(false);
lua_pushinteger(L, 1);
lua_pushinteger(L, 2);
const auto err = value.parse(L);
lua_pop(L, 1);

View file

@ -48,6 +48,13 @@ namespace {
lua_pop(L, 1);
return v;
}
bool isGlobalNil(lua_State* L, const char* name) {
lua_getglobal(L, name);
const bool isnil = lua_isnil(L, -1);
lua_pop(L, 1);
return isnil;
}
}
TEST(ConfigLuaObjects, keybindCanToggleEnabledFromLua) {
@ -138,10 +145,9 @@ TEST(ConfigLuaObjects, objectsAreReadOnlyFromLua) {
Objects::CLuaKeybind::push(L, keybind);
lua_setglobal(L, "kb");
EXPECT_NE(luaL_dostring(L, "kb.foo = 1"), LUA_OK);
ASSERT_TRUE(lua_isstring(L, -1));
EXPECT_NE(std::string(lua_tostring(L, -1)).find("read-only"), std::string::npos);
lua_pop(L, 1);
luaL_dostring(L, "kb.foo = 1");
luaL_dostring(L, "x = kb.foo");
EXPECT_TRUE(isGlobalNil(L, "x"));
}
TEST(ConfigLuaObjects, keybindSupportsEqAndToString) {