config/propRefresher: add capability to execute scheduled prop refresh immediately with lua helper function, add event for prop refresh (#14990)

* Revert https://github.com/hyprwm/Hyprland/pull/14594

* add `executeScheduledRefreshImmediately()`, `refreshProp()`

* add exec_scheduled_prop_refresh_immediately helper function

* add event

add return value for exec_scheduled_prop_refresh_immediately

* format

* test
This commit is contained in:
erstarr
2026-06-17 14:55:31 +01:00
committed by GitHub
parent 5b47c782c9
commit 2d190ba79a
9 changed files with 146 additions and 114 deletions
+5 -5
View File
@@ -1030,10 +1030,9 @@ TEST_CASE(testScrollingViewBehaviourMoveFocusInGroupFollowFocusTrue) {
TEST_CASE(testScrollingViewBehaviourScheduledPropRefresh) {
/*
Scheduled prop refresh must not move scrolling viewport.
The reason a prop refresh was queued is not saved, therefore it is not possible to clearly tell when and when not to move scrolling viewport
In this test, we test this by setting a workspace rule, which schedules a prop refresh
--------------------------------------------------------------------------------------------------------------------------------------
Test that hl.exec_scheduled_prop_refresh_immediately() should immediately execute prop refresh. This is tested via inhibiting scrollin during helper functs dispatch; if it works, the viewport
should not move when a new workspace rule is created. If it doesn't, dispatch will miss because the refresh will be executed as another event
--------------------------------------------------------------------------------------------------------------------------------------
*/
OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })"));
@@ -1058,7 +1057,8 @@ TEST_CASE(testScrollingViewBehaviourScheduledPropRefresh) {
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
// setting a workspace rule queues a doLater() call in the Event Loop Manager
OK(getFromSocket("/eval hl.workspace_rule({workspace = hl.get_active_workspace().id,gaps_in = 0})"));
OK(getFromSocket("/eval hl.dispatch(hl.dsp.layout('inhibit_scroll true')); hl.workspace_rule({workspace = hl.get_active_workspace().id,gaps_in = 0}); "
"hl.exec_scheduled_prop_refresh_immediately(); hl.dispatch(hl.dsp.layout('inhibit_scroll false'));"));
// Check that the workspace rule is set
ASSERT_CONTAINS(getFromSocket("/workspacerules"), "gapsIn: 0 0 0 0");
+4
View File
@@ -148,6 +148,9 @@ CLuaEventHandler::CLuaEventHandler(lua_State* L) : m_lua(L) {
}));
m_listeners.push_back(bus()->m_events.config.reloaded.listen([this] { dispatch("config.reloaded", 0, [](lua_State* L) {}); }));
m_listeners.push_back(bus()->m_events.config.props_refreshed.listen(
[this](const bool execdAsScheduled) { dispatch("config.props_refreshed", 1, [&](lua_State* L) { lua_pushboolean(L, sc<lua_Integer>(execdAsScheduled)); }); }));
m_listeners.push_back(
bus()->m_events.keybinds.submap.listen([this](const std::string& submap) { dispatch("keybinds.submap", 1, [&](lua_State* L) { lua_pushstring(L, submap.c_str()); }); }));
m_listeners.push_back(bus()->m_events.screenshare.state.listen([this](bool state, uint8_t type, const std::string& name) {
@@ -277,6 +280,7 @@ std::unordered_set<std::string> CLuaEventHandler::knownEvents() {
"workspace.removed",
"workspace.move_to_monitor",
"config.reloaded",
"config.props_refreshed",
"keybinds.submap",
"screenshare.state",
"hyprland.start",
@@ -462,6 +462,11 @@ static int hlTimer(lua_State* L) {
return 1;
}
static int hlExecuteScheduledRefreshImmediately(lua_State* L) {
return Supplementary::refresher()->executeScheduledRefreshImmediately();
}
void Internal::registerToplevelBindings(lua_State* L, CConfigManager* mgr) {
Internal::setMgrFn(L, mgr, "on", hlOn);
Internal::setMgrFn(L, mgr, "bind", hlBind);
@@ -473,5 +478,7 @@ void Internal::registerToplevelBindings(lua_State* L, CConfigManager* mgr) {
Internal::setFn(L, "get_loaded_plugins", hlGetPlugins);
Internal::setFn(L, "exec_cmd", hlExecCmd);
Internal::setFn(L, "exec_scheduled_prop_refresh_immediately", hlExecuteScheduledRefreshImmediately);
Internal::setFn(L, "unbind", hlUnbind);
}
@@ -15,6 +15,7 @@
#include "../../shared/monitor/MonitorRuleManager.hpp"
#include "../../shared/inotify/ConfigWatcher.hpp"
#include "../../../event/EventBus.hpp"
using namespace Config;
using namespace Config::Supplementary;
@@ -25,111 +26,130 @@ UP<CPropRefresher>& Supplementary::refresher() {
}
void CPropRefresher::scheduleRefresh(PropRefreshBits prop) {
static auto PZOOMFACTOR = CConfigValue<Config::FLOAT>("cursor:zoom_factor");
m_propsTripped |= prop;
if (!m_scheduled && g_pEventLoopManager) {
g_pEventLoopManager->doLater([this, weak = WP<CPropRefresher>{refresher()}] {
m_scheduledRefreshSeq = g_pEventLoopManager->doLater([this, weak = WP<CPropRefresher>{refresher()}] {
if (!weak)
return;
if (m_propsTripped & REFRESH_INPUT_DEVICES) {
g_pInputManager->setKeyboardLayout(); // update kb layout
g_pInputManager->setPointerConfigs(); // update mouse cfgs
g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs
g_pInputManager->setTabletConfigs(); // update tablets
g_pInputManager->setTabletToolConfigs(); // update tablettools
}
if (m_propsTripped & REFRESH_SCREEN_SHADER) {
g_pHyprRenderer->m_reloadScreenShader = true;
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_forceFullFrames = 2;
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_BLUR_FB) {
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_blurFBDirty = true;
m->m_forceFullFrames = 2;
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_WINDOW_STATES) {
Desktop::Rule::ruleEngine()->updateAllRules();
for (const auto& ws : State::workspaceState()->workspaces()) {
if (!ws)
continue;
ws->updateWindows();
ws->updateWindowData();
ws->updateWindowDecos();
}
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_forceFullFrames = 2;
g_pHyprRenderer->damageMonitor(m);
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_MONITOR_STATES) {
Config::monitorRuleMgr()->scheduleReload();
Config::monitorRuleMgr()->ensureVRR();
for (const auto& m : State::monitorState()->monitors()) {
if (!m)
continue;
g_layoutManager->recalculateMonitor(m, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_PROP_REFRESH);
}
State::workspacePlacementController()->ensurePersistentWorkspacesPresent(
nullptr, [](PHLWORKSPACE ws, PHLMONITOR mon, bool noWarp) { g_pCompositor->moveWorkspaceToMonitor(ws, mon, noWarp); });
}
if (m_propsTripped & REFRESH_LAYOUTS) {
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
for (auto const& m : State::monitorState()->monitors()) {
g_layoutManager->recalculateMonitor(m, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_PROP_REFRESH);
g_pHyprRenderer->damageMonitor(m);
}
}
if (m_propsTripped & REFRESH_CURSOR_ZOOMS) {
for (auto const& m : State::monitorState()->monitors()) {
*(m->m_cursorZoom) = *PZOOMFACTOR;
if (m->m_activeWorkspace)
m->m_activeWorkspace->m_space->recalculate();
}
}
if (m_propsTripped & REFRESH_CONFIG_WATCHER)
Config::watcher()->update();
if (m_propsTripped & REFRESH_GRADIENTS_GROUPBAR)
refreshGroupBarGradients();
m_scheduled = false;
m_propsTripped = 0;
refreshProp(true);
});
m_scheduled = true;
}
}
int CPropRefresher::executeScheduledRefreshImmediately() {
if (!m_scheduled || m_scheduledRefreshSeq == 0)
return 1;
g_pEventLoopManager->removeDoLater(m_scheduledRefreshSeq);
// m_scheduledRefreshSeq must be reset back to 0 during refreshProp() call
refreshProp(false);
return 0;
}
void CPropRefresher::refreshProp(const bool execdAsScheduled) {
static auto PZOOMFACTOR = CConfigValue<Config::FLOAT>("cursor:zoom_factor");
if (m_propsTripped & REFRESH_INPUT_DEVICES) {
g_pInputManager->setKeyboardLayout(); // update kb layout
g_pInputManager->setPointerConfigs(); // update mouse cfgs
g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs
g_pInputManager->setTabletConfigs(); // update tablets
g_pInputManager->setTabletToolConfigs(); // update tablettools
}
if (m_propsTripped & REFRESH_SCREEN_SHADER) {
g_pHyprRenderer->m_reloadScreenShader = true;
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_forceFullFrames = 2;
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_BLUR_FB) {
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_blurFBDirty = true;
m->m_forceFullFrames = 2;
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_WINDOW_STATES) {
Desktop::Rule::ruleEngine()->updateAllRules();
for (const auto& ws : State::workspaceState()->workspaces()) {
if (!ws)
continue;
ws->updateWindows();
ws->updateWindowData();
ws->updateWindowDecos();
}
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
for (auto const& m : State::monitorState()->monitors()) {
if (!m)
continue;
m->m_forceFullFrames = 2;
g_pHyprRenderer->damageMonitor(m);
m->scheduleFrame();
}
}
if (m_propsTripped & REFRESH_MONITOR_STATES) {
Config::monitorRuleMgr()->scheduleReload();
Config::monitorRuleMgr()->ensureVRR();
for (const auto& m : State::monitorState()->monitors()) {
if (!m)
continue;
g_layoutManager->recalculateMonitor(m);
}
State::workspacePlacementController()->ensurePersistentWorkspacesPresent(
nullptr, [](PHLWORKSPACE ws, PHLMONITOR mon, bool noWarp) { g_pCompositor->moveWorkspaceToMonitor(ws, mon, noWarp); });
}
if (m_propsTripped & REFRESH_LAYOUTS) {
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
for (auto const& m : State::monitorState()->monitors()) {
g_layoutManager->recalculateMonitor(m);
g_pHyprRenderer->damageMonitor(m);
}
}
if (m_propsTripped & REFRESH_CURSOR_ZOOMS) {
for (auto const& m : State::monitorState()->monitors()) {
*(m->m_cursorZoom) = *PZOOMFACTOR;
if (m->m_activeWorkspace)
m->m_activeWorkspace->m_space->recalculate();
}
}
if (m_propsTripped & REFRESH_CONFIG_WATCHER)
Config::watcher()->update();
if (m_propsTripped & REFRESH_GRADIENTS_GROUPBAR)
refreshGroupBarGradients();
m_scheduled = false;
m_scheduledRefreshSeq = 0;
m_propsTripped = 0;
Event::bus()->m_events.config.props_refreshed.emit(execdAsScheduled);
}
@@ -26,10 +26,14 @@ namespace Config::Supplementary {
class CPropRefresher {
public:
void scheduleRefresh(PropRefreshBits reason);
int executeScheduledRefreshImmediately();
private:
bool m_scheduled = false;
PropRefreshBits m_propsTripped = 0;
void refreshProp(const bool execdAsScheduled);
bool m_scheduled = false;
uint64_t m_scheduledRefreshSeq = 0; // 0 if no refresh event scheduled
PropRefreshBits m_propsTripped = 0;
};
UP<CPropRefresher>& refresher();
+3 -2
View File
@@ -165,8 +165,9 @@ namespace Event {
} workspace;
struct {
Event<> preReload;
Event<> reloaded;
Event<> preReload;
Event<> reloaded;
Event<const bool> props_refreshed;
} config;
struct {
-1
View File
@@ -52,7 +52,6 @@ namespace Layout {
enum eRecalculateMonitorReason : uint8_t {
RECALCULATE_MONITOR_REASON_UNKNOWN, // when the recalculate monitor reason is unknown or not important to preserve
RECALCULATE_MONITOR_REASON_PROP_REFRESH,
RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE,
RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE,
RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN,
+4 -5
View File
@@ -32,7 +32,7 @@ CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) {
recheckWorkArea();
if (m_algorithm)
m_algorithm->recalculate(RECALCULATE_REASON_CREATE_SPACE);
m_algorithm->recalculate();
});
}
@@ -219,9 +219,9 @@ SP<ITarget> CSpace::getNextCandidate(SP<ITarget> old) {
}
bool Layout::isHardRecalculateReason(eRecalculateReason reason) {
return reason != RECALCULATE_REASON_CREATE_SPACE && reason != RECALCULATE_REASON_PROP_REFRESH && reason != RECALCULATE_REASON_WORKSPACE_CHANGE &&
reason != RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE && reason != RECALCULATE_REASON_TOGGLE_LAYOUT_HANDLED_FULLSCREEN &&
reason != RECALCULATE_REASON_TOGGLE_DEFAULT_HANDLED_FULLSCREEN && reason != RECALCULATE_REASON_INVALIDATE_MONITOR_GEOMETRIES && reason != RECALCULATE_REASON_RENDER_MOINTOR;
return reason != RECALCULATE_REASON_WORKSPACE_CHANGE && reason != RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE &&
reason != RECALCULATE_REASON_TOGGLE_LAYOUT_HANDLED_FULLSCREEN && reason != RECALCULATE_REASON_TOGGLE_DEFAULT_HANDLED_FULLSCREEN &&
reason != RECALCULATE_REASON_INVALIDATE_MONITOR_GEOMETRIES && reason != RECALCULATE_REASON_RENDER_MOINTOR;
}
const std::vector<WP<ITarget>>& CSpace::targets() const {
@@ -234,7 +234,6 @@ eRecalculateReason Layout::recalcMonitorReasonToRecalcReason(CLayoutManager::eRe
case CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE: return RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE;
case CLayoutManager::RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE: return RECALCULATE_REASON_WORKSPACE_CHANGE;
case CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN: return RECALCULATE_REASON_TOGGLE_DEFAULT_HANDLED_FULLSCREEN;
case CLayoutManager::RECALCULATE_MONITOR_REASON_PROP_REFRESH: return RECALCULATE_REASON_PROP_REFRESH;
default: return RECALCULATE_REASON_UNKNOWN;
}
}
-2
View File
@@ -15,8 +15,6 @@ namespace Layout {
enum eRecalculateReason : uint8_t {
RECALCULATE_REASON_UNKNOWN, // when the recalculate reason is unknown or not important to preserve
RECALCULATE_REASON_PROP_REFRESH,
RECALCULATE_REASON_CREATE_SPACE,
RECALCULATE_REASON_WORKSPACE_CHANGE,
RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE,
RECALCULATE_REASON_TOGGLE_DEFAULT_HANDLED_FULLSCREEN,