mirror of
https://github.com/hyprwm/hypridle.git
synced 2026-05-07 13:48:00 +02:00
core: add per-listener condition_cmd with retry
Add condition_cmd and condition_retry config options per listener. When set, condition_cmd is executed before on-timeout fires. Exit 0 proceeds normally, non-zero defers on-timeout and retries every condition_retry seconds while the user remains idle. User activity cancels any pending retry. This allows gating actions (e.g. suspend) on external conditions like active SSH sessions or running workloads without external inhibitor daemons.
This commit is contained in:
parent
91ab4f0dcc
commit
e24372277c
4 changed files with 94 additions and 14 deletions
|
|
@ -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<Hyprlang::STRING>(m_config.getSpecialConfigValue("listener", "on-resume", k.c_str()));
|
||||
|
||||
rule.ignoreInhibit = std::any_cast<Hyprlang::INT>(m_config.getSpecialConfigValue("listener", "ignore_inhibit", k.c_str()));
|
||||
rule.conditionCmd = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("listener", "condition_cmd", k.c_str()));
|
||||
rule.conditionRetry = std::any_cast<Hyprlang::INT>(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;
|
||||
|
|
|
|||
|
|
@ -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<STimeoutRule> getRules();
|
||||
|
|
|
|||
|
|
@ -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::seconds>(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) {
|
||||
|
|
|
|||
|
|
@ -17,11 +17,15 @@ class CHypridle {
|
|||
CHypridle();
|
||||
|
||||
struct SIdleListener {
|
||||
SP<CCExtIdleNotificationV1> notification = nullptr;
|
||||
std::string onTimeout = "";
|
||||
std::string onRestore = "";
|
||||
bool ignoreInhibit = false;
|
||||
bool onTimeoutFired = false;
|
||||
SP<CCExtIdleNotificationV1> 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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue