diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a5c0a3a..372a524 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -39,6 +39,8 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("listener", "on-timeout", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("listener", "on-resume", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("listener", "ignore_inhibit", Hyprlang::INT{0}); + m_config.addSpecialConfigValue("listener", "condition_cmd", Hyprlang::STRING{""}); + m_config.addSpecialConfigValue("listener", "condition_retry", Hyprlang::INT{30}); m_config.addConfigValue("general:lock_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:unlock_cmd", Hyprlang::STRING{""}); @@ -88,6 +90,8 @@ Hyprlang::CParseResult CConfigManager::postParse() { rule.onResume = std::any_cast(m_config.getSpecialConfigValue("listener", "on-resume", k.c_str())); rule.ignoreInhibit = std::any_cast(m_config.getSpecialConfigValue("listener", "ignore_inhibit", k.c_str())); + rule.conditionCmd = std::any_cast(m_config.getSpecialConfigValue("listener", "condition_cmd", k.c_str())); + rule.conditionRetry = std::any_cast(m_config.getSpecialConfigValue("listener", "condition_retry", k.c_str())); if (timeout == -1) { result.setError("Category has a missing timeout setting"); @@ -98,8 +102,8 @@ Hyprlang::CParseResult CConfigManager::postParse() { } for (auto& r : m_vRules) { - Debug::log(LOG, "Registered timeout rule for {}s:\n on-timeout: {}\n on-resume: {}\n ignore_inhibit: {}", r.timeout, r.onTimeout, r.onResume, - r.ignoreInhibit); + Debug::log(LOG, "Registered timeout rule for {}s:\n on-timeout: {}\n on-resume: {}\n ignore_inhibit: {}\n condition_cmd: {}\n condition_retry: {}", + r.timeout, r.onTimeout, r.onResume, r.ignoreInhibit, r.conditionCmd, r.conditionRetry); } return result; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index b4f4636..786463f 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -14,10 +14,12 @@ class CConfigManager { void init(); struct STimeoutRule { - uint64_t timeout = 0; - std::string onTimeout = ""; - std::string onResume = ""; - bool ignoreInhibit = false; + uint64_t timeout = 0; + std::string onTimeout = ""; + std::string onResume = ""; + bool ignoreInhibit = false; + std::string conditionCmd = ""; + int64_t conditionRetry = 30; }; std::vector getRules(); diff --git a/src/core/Hypridle.cpp b/src/core/Hypridle.cpp index 3519a9d..9d528f9 100644 --- a/src/core/Hypridle.cpp +++ b/src/core/Hypridle.cpp @@ -65,9 +65,11 @@ void CHypridle::run() { for (size_t i = 0; i < RULES.size(); ++i) { auto& l = m_sWaylandIdleState.listeners[i]; const auto& r = RULES[i]; - l.onRestore = r.onResume; - l.onTimeout = r.onTimeout; - l.ignoreInhibit = r.ignoreInhibit; + l.onRestore = r.onResume; + l.onTimeout = r.onTimeout; + l.ignoreInhibit = r.ignoreInhibit; + l.conditionCmd = r.conditionCmd; + l.conditionRetry = r.conditionRetry; if (*IGNOREWAYLANDINHIBIT || r.ignoreInhibit) l.notification = @@ -142,6 +144,45 @@ void CHypridle::run() { enterEventLoop(); } +static int64_t nowMonotonic() { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +static bool runConditionCmd(const std::string& cmd) { + Debug::log(LOG, "Running condition_cmd: {}", cmd); + + pid_t pid = fork(); + if (pid < 0) { + Debug::log(ERR, "Failed to fork for condition_cmd"); + return false; + } + + if (pid == 0) { + execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), nullptr); + _exit(127); + } + + // Wait with 5s timeout + for (int i = 0; i < 50; i++) { + int status = 0; + pid_t ret = waitpid(pid, &status, WNOHANG); + if (ret > 0) { + int exitCode = WIFEXITED(status) ? WEXITSTATUS(status) : 1; + Debug::log(LOG, "condition_cmd exited with {}", exitCode); + return exitCode == 0; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Timeout — kill and return false + Debug::log(WARN, "condition_cmd timed out after 5s, killing"); + kill(pid, SIGKILL); + waitpid(pid, nullptr, 0); + return false; +} + +static void spawn(const std::string& args); + void CHypridle::enterEventLoop() { nfds_t pollfdsCount = m_sDBUSState.screenSaverServiceConnection ? 3 : 2; @@ -238,6 +279,24 @@ void CHypridle::enterEventLoop() { ret = wl_display_dispatch_pending(m_sWaylandState.display); wl_display_flush(m_sWaylandState.display); } while (ret > 0); + + // Check condition_cmd retries for pending listeners + const auto now = nowMonotonic(); + for (auto& l : m_sWaylandIdleState.listeners) { + if (!l.conditionPending || now < l.conditionRetryAt) + continue; + + Debug::log(LOG, "Retrying condition_cmd for rule {:x}", (uintptr_t)&l); + if (runConditionCmd(l.conditionCmd)) { + l.conditionPending = false; + l.onTimeoutFired = true; + Debug::log(LOG, "Condition met, running {}", l.onTimeout); + spawn(l.onTimeout); + } else { + l.conditionRetryAt = now + l.conditionRetry; + Debug::log(LOG, "Condition still not met, retrying in {}s", l.conditionRetry); + } + } } Debug::log(ERR, "[core] Terminated"); @@ -268,6 +327,16 @@ void CHypridle::onIdled(SIdleListener* pListener) { return; } + // Check condition_cmd before firing on-timeout + if (!pListener->conditionCmd.empty()) { + if (!runConditionCmd(pListener->conditionCmd)) { + Debug::log(LOG, "condition_cmd blocked on-timeout, retrying in {}s", pListener->conditionRetry); + pListener->conditionPending = true; + pListener->conditionRetryAt = nowMonotonic() + pListener->conditionRetry; + return; + } + } + Debug::log(LOG, "Running {}", pListener->onTimeout); pListener->onTimeoutFired = true; spawn(pListener->onTimeout); @@ -276,6 +345,7 @@ void CHypridle::onIdled(SIdleListener* pListener) { void CHypridle::onResumed(SIdleListener* pListener) { Debug::log(LOG, "Resumed: rule {:x}", (uintptr_t)pListener); isIdled = false; + pListener->conditionPending = false; // If on-timeout never actually executed (was inhibited), skip on-resume too if (!pListener->onTimeoutFired) { diff --git a/src/core/Hypridle.hpp b/src/core/Hypridle.hpp index c64d95b..4fe5db0 100644 --- a/src/core/Hypridle.hpp +++ b/src/core/Hypridle.hpp @@ -17,11 +17,15 @@ class CHypridle { CHypridle(); struct SIdleListener { - SP notification = nullptr; - std::string onTimeout = ""; - std::string onRestore = ""; - bool ignoreInhibit = false; - bool onTimeoutFired = false; + SP notification = nullptr; + std::string onTimeout = ""; + std::string onRestore = ""; + bool ignoreInhibit = false; + bool onTimeoutFired = false; + std::string conditionCmd = ""; + int64_t conditionRetry = 30; + bool conditionPending = false; + int64_t conditionRetryAt = 0; }; struct SDbusInhibitCookie {