Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hypridle for openSUSE:Factory checked in at 2026-08-01 18:31:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hypridle (Old) and /work/SRC/openSUSE:Factory/.hypridle.new.16738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hypridle" Sat Aug 1 18:31:31 2026 rev:5 rq:1368701 version:0.1.8 Changes: -------- --- /work/SRC/openSUSE:Factory/hypridle/hypridle.changes 2025-09-22 16:41:43.562570005 +0200 +++ /work/SRC/openSUSE:Factory/.hypridle.new.16738/hypridle.changes 2026-08-01 18:34:30.190514853 +0200 @@ -1,0 +2,12 @@ +Thu Jul 30 19:01:58 UTC 2026 - Nassim Lhadj <[email protected]> + +- Update to version 0.1.8: + * core: fix dbus inhibit lock counting + * nix: remove sdbus-cpp overlay + * core: don't skip on-resume when on-timeout already fired + * Fix: allow hypridle -c to run without default conf + * config: update dpms commands to lua syntax + * core: implement GetActive and ActiveChanged on dbus service + * core: add per-listener condition_cmd with retry + +------------------------------------------------------------------- Old: ---- hypridle-0.1.7.obscpio New: ---- hypridle-0.1.8.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hypridle.spec ++++++ --- /var/tmp/diff_new_pack.gTh1vu/_old 2026-08-01 18:34:32.046578755 +0200 +++ /var/tmp/diff_new_pack.gTh1vu/_new 2026-08-01 18:34:32.050578892 +0200 @@ -1,7 +1,7 @@ # # spec file for package hypridle # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: hypridle -Version: 0.1.7 +Version: 0.1.8 Release: 0 Summary: Hyprland's idle daemon License: BSD-3-Clause ++++++ hypridle-0.1.7.obscpio -> hypridle-0.1.8.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/.github/workflows/nix.yml new/hypridle-0.1.8/.github/workflows/nix.yml --- old/hypridle-0.1.7/.github/workflows/nix.yml 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/.github/workflows/nix.yml 2026-07-26 16:44:59.000000000 +0200 @@ -1,48 +1,11 @@ name: Build on: [push, pull_request, workflow_dispatch] + jobs: nix: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Install Nix - uses: nixbuild/nix-quick-install-action@v31 - with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 - with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }}- - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - # 1G = 1073741824 - gc-max-store-size-linux: 1G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }}- - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never - - # not needed (yet) - # - uses: cachix/cachix-action@v12 - # with: - # name: hyprland - # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - - - name: Build - run: nix flake check --print-build-logs --keep-going - + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) + uses: hyprwm/actions/.github/workflows/nix.yml@main + secrets: inherit + with: + command: nix build --print-build-logs --keep-going diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/README.md new/hypridle-0.1.8/README.md --- old/hypridle-0.1.7/README.md 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/README.md 2026-07-26 16:44:59.000000000 +0200 @@ -25,6 +25,8 @@ timeout = 500 # in seconds on-timeout = notify-send "You are idle!" # command to run when timeout has passed on-resume = notify-send "Welcome back!" # command to run when activity is detected after timeout has fired. + condition_cmd = # if set, run this command before on-timeout. Exit 0 = proceed, non-zero = defer + condition_retry = 0 # retry interval in seconds when condition_cmd defers (default: 0, no retry) } ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/VERSION new/hypridle-0.1.8/VERSION --- old/hypridle-0.1.7/VERSION 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/VERSION 2026-07-26 16:44:59.000000000 +0200 @@ -1 +1 @@ -0.1.7 +0.1.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/assets/example.conf new/hypridle-0.1.8/assets/example.conf --- old/hypridle-0.1.7/assets/example.conf 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/assets/example.conf 2026-07-26 16:44:59.000000000 +0200 @@ -2,9 +2,9 @@ # for more configuration options, refer https://wiki.hyprland.org/Hypr-Ecosystem/hypridle general { - lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances. - before_sleep_cmd = loginctl lock-session # lock before suspend. - after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display. + lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances. + before_sleep_cmd = loginctl lock-session # lock before suspend. + after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # turn on screen w/o key press after resume. } listener { @@ -26,9 +26,9 @@ } listener { - timeout = 330 # 5.5min. - on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed. - on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired. + timeout = 330 # 5.5min. + on-timeout = hyprctl dispatch 'hl.dsp.dpms({action = "off"})' # screen off when timeout has passed. + on-resume = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # screen on when activity is detected. } listener { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/flake.lock new/hypridle-0.1.8/flake.lock --- old/hypridle-0.1.7/flake.lock 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/flake.lock 2026-07-26 16:44:59.000000000 +0200 @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1749046714, - "narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=", + "lastModified": 1772460177, + "narHash": "sha256-/6G/MsPvtn7bc4Y32pserBT/Z4SUUdBd4XYJpOEKVR4=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330", + "rev": "1cb6db5fd6bb8aee419f4457402fa18293ace917", "type": "github" }, "original": { @@ -36,11 +36,11 @@ ] }, "locked": { - "lastModified": 1749145882, - "narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=", + "lastModified": 1772459629, + "narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676", + "rev": "7615ee388de18239a4ab1400946f3d0e498a8186", "type": "github" }, "original": { @@ -59,11 +59,11 @@ ] }, "locked": { - "lastModified": 1749135356, - "narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=", + "lastModified": 1772459870, + "narHash": "sha256-xxkK2Cvqxpf/4UGcJ/TyCwrvmiNWsKsJfFzHMp2bxis=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb", + "rev": "e63f3a79334dec49f8eb1691f66f18115df04085", "type": "github" }, "original": { @@ -82,11 +82,11 @@ ] }, "locked": { - "lastModified": 1749145760, - "narHash": "sha256-IHaGWpGrv7seFWdw/1A+wHtTsPlOGIKMrk1TUIYJEFI=", + "lastModified": 1772459835, + "narHash": "sha256-978jRz/y/9TKmZb/qD4lEYHCQGHpEXGqy+8X2lFZsak=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "817918315ea016cc2d94004bfb3223b5fd9dfcc6", + "rev": "0a692d4a645165eebd65f109146b8861e3a925e7", "type": "github" }, "original": { @@ -97,11 +97,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748929857, - "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", + "lastModified": 1772198003, + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "type": "github" }, "original": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/flake.nix new/hypridle-0.1.8/flake.nix --- old/hypridle-0.1.7/flake.nix 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/flake.nix 2026-07-26 16:44:59.000000000 +0200 @@ -31,35 +31,40 @@ }; }; - outputs = { - self, - nixpkgs, - systems, - ... - } @ inputs: let - inherit (nixpkgs) lib; - eachSystem = lib.genAttrs (import systems); - - pkgsFor = eachSystem (system: - import nixpkgs { - localSystem.system = system; - overlays = with self.overlays; [default]; + outputs = + { + self, + nixpkgs, + systems, + ... + }@inputs: + let + inherit (nixpkgs) lib; + eachSystem = lib.genAttrs (import systems); + + pkgsFor = eachSystem ( + system: + import nixpkgs { + localSystem.system = system; + overlays = with self.overlays; [ hypridle-with-deps ]; + } + ); + in + { + overlays = import ./nix/overlays.nix { inherit inputs lib self; }; + + packages = eachSystem (system: { + default = self.packages.${system}.hypridle; + inherit (pkgsFor.${system}) hypridle; }); - in { - overlays = import ./nix/overlays.nix {inherit inputs lib;}; - packages = eachSystem (system: { - default = self.packages.${system}.hypridle; - inherit (pkgsFor.${system}) hypridle; - }); - - homeManagerModules = { - default = self.homeManagerModules.hypridle; - hypridle = builtins.throw "hypridle: the flake HM module has been removed. Use the module from Home Manager upstream."; - }; + homeManagerModules = { + default = self.homeManagerModules.hypridle; + hypridle = builtins.throw "hypridle: the flake HM module has been removed. Use the module from Home Manager upstream."; + }; - checks = eachSystem (system: self.packages.${system}); + checks = eachSystem (system: self.packages.${system}); - formatter = eachSystem (system: pkgsFor.${system}.alejandra); - }; + formatter = eachSystem (system: pkgsFor.${system}.nixfmt-tree); + }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/nix/default.nix new/hypridle-0.1.8/nix/default.nix --- old/hypridle-0.1.7/nix/default.nix 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/nix/default.nix 2026-07-26 16:44:59.000000000 +0200 @@ -6,7 +6,7 @@ hyprwayland-scanner, lib, pkg-config, - sdbus-cpp, + sdbus-cpp_2, stdenv, systemd, wayland, @@ -30,7 +30,7 @@ hyprland-protocols hyprlang hyprutils - sdbus-cpp + sdbus-cpp_2 systemd wayland wayland-protocols diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/nix/overlays.nix new/hypridle-0.1.8/nix/overlays.nix --- old/hypridle-0.1.7/nix/overlays.nix 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/nix/overlays.nix 2026-07-26 16:44:59.000000000 +0200 @@ -1,42 +1,41 @@ { lib, inputs, -}: let - mkDate = longDate: (lib.concatStringsSep "-" [ - (builtins.substring 0 4 longDate) - (builtins.substring 4 2 longDate) - (builtins.substring 6 2 longDate) - ]); + self, + ... +}: +let + mkDate = + longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); -in { +in +{ default = inputs.self.overlays.hypridle; - hypridle = lib.composeManyExtensions [ + hypridle-with-deps = lib.composeManyExtensions [ inputs.hyprland-protocols.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default - inputs.self.overlays.sdbuscpp - (final: prev: { - hypridle = prev.callPackage ./default.nix { - stdenv = prev.gcc15Stdenv; - version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); - inherit (final) hyprlang; - }; - }) + self.overlays.hypridle ]; - sdbuscpp = final: prev: { - sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: { - version = "2.0.0"; - - src = final.fetchFromGitHub { - owner = "Kistler-group"; - repo = "sdbus-cpp"; - rev = "refs/tags/v${self.version}"; - hash = "sha256-W8V5FRhV3jtERMFrZ4gf30OpIQLYoj2yYGpnYOmH2+g="; - }; - }); + hypridle = final: prev: { + hypridle = prev.callPackage ./default.nix { + stdenv = prev.gcc15Stdenv; + version = + version + + "+date=" + + (mkDate (inputs.self.lastModifiedDate or "19700101")) + + "_" + + (inputs.self.shortRev or "dirty"); + inherit (final) hyprlang; + }; }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/src/config/ConfigManager.cpp new/hypridle-0.1.8/src/config/ConfigManager.cpp --- old/hypridle-0.1.7/src/config/ConfigManager.cpp 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/src/config/ConfigManager.cpp 2026-07-26 16:44:59.000000000 +0200 @@ -5,19 +5,41 @@ #include <filesystem> #include <glob.h> #include <cstring> +#include <expected> + +static std::expected<std::string, std::error_code> getMainConfigPath(const std::string& overridePath = "") { + namespace fs = std::filesystem; + + if (!overridePath.empty()) { + std::error_code ec; + if (!fs::exists(overridePath, ec)) + return std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory)); + return overridePath; + } -static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hypridle"); + if (paths.first.has_value()) return paths.first.value(); - else - throw std::runtime_error("Could not find config in HOME, XDG_CONFIG_HOME, XDG_CONFIG_DIRS or /etc/hypr."); + + return std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory)); } CConfigManager::CConfigManager(std::string configPath) : - m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = false}) { - ; - configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath; + /* allowMissingConfig = 'true' to avoid library-level exceptions during initialization. + Existence is guaranteed by the path check below. */ + m_config(getMainConfigPath(configPath).value_or("").c_str(), + Hyprlang::SConfigOptions{.throwAllErrors = false, .allowMissingConfig = true}) +{ + auto pathResult = getMainConfigPath(configPath); + + if (!pathResult) + return; + + configCurrentPath = *pathResult; + configHeadPath = configCurrentPath; + + Debug::log(LOG, "Using config file: {}", configCurrentPath); } static Hyprlang::CParseResult handleSource(const char* c, const char* v) { @@ -38,6 +60,8 @@ 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{0}); m_config.addConfigValue("general:lock_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:unlock_cmd", Hyprlang::STRING{""}); @@ -51,8 +75,7 @@ m_config.addConfigValue("general:inhibit_sleep", Hyprlang::INT{2}); // track the file in the circular dependency chain - const auto mainConfigPath = getMainConfigPath(); - alreadyIncludedSourceFiles.insert(std::filesystem::canonical(mainConfigPath)); + alreadyIncludedSourceFiles.insert(std::filesystem::canonical(configHeadPath)); m_config.registerHandler(&::handleSource, "source", {.allowFlags = false}); @@ -88,6 +111,8 @@ 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 +123,8 @@ } 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; @@ -110,9 +135,9 @@ } std::optional<std::string> CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { - if (rawpath.length() < 2) { + if (rawpath.length() < 2) return "source path " + rawpath + " bogus!"; - } + std::unique_ptr<glob_t, void (*)(glob_t*)> glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; memset(glob_buf.get(), 0, sizeof(glob_t)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/src/config/ConfigManager.hpp new/hypridle-0.1.8/src/config/ConfigManager.hpp --- old/hypridle-0.1.7/src/config/ConfigManager.hpp 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/src/config/ConfigManager.hpp 2026-07-26 16:44:59.000000000 +0200 @@ -14,15 +14,17 @@ 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 = 0; }; std::vector<STimeoutRule> getRules(); std::optional<std::string> handleSource(const std::string&, const std::string&); - std::string configCurrentPath; + std::string configCurrentPath, configHeadPath; std::set<std::string> alreadyIncludedSourceFiles; template <typename T> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/src/core/Hypridle.cpp new/hypridle-0.1.8/src/core/Hypridle.cpp --- old/hypridle-0.1.7/src/core/Hypridle.cpp 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/src/core/Hypridle.cpp 2026-07-26 16:44:59.000000000 +0200 @@ -65,9 +65,11 @@ 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,26 @@ 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); + + Hyprutils::OS::CProcess proc("/bin/sh", {"-c", cmd}); + if (!proc.runSync()) { + Debug::log(ERR, "Failed to run condition_cmd: {}", cmd); + return false; + } + + const int exitCode = proc.exitCode(); + Debug::log(LOG, "condition_cmd exited with {}", exitCode); + return exitCode == 0; +} + +static void spawn(const std::string& args); + void CHypridle::enterEventLoop() { nfds_t pollfdsCount = m_sDBUSState.screenSaverServiceConnection ? 3 : 2; @@ -196,7 +218,7 @@ std::unique_lock lk(m_sEventLoopInternals.loopMutex); if (!m_sEventLoopInternals.shouldProcess) // avoid a lock if a thread managed to request something already since we .unlock()ed - m_sEventLoopInternals.loopSignal.wait(lk, [this] { return m_sEventLoopInternals.shouldProcess == true; }); // wait for events + m_sEventLoopInternals.loopSignal.wait_for(lk, std::chrono::seconds(5), [this] { return m_sEventLoopInternals.shouldProcess == true; }); // wait for events or timeout (for condition_cmd retries) m_sEventLoopInternals.loopRequestMutex.lock(); // lock incoming events @@ -238,6 +260,24 @@ 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,18 +308,49 @@ return; } + // Check condition_cmd before firing on-timeout + if (!pListener->conditionCmd.empty()) { + if (!runConditionCmd(pListener->conditionCmd)) { + if (pListener->conditionRetry > 0) { + Debug::log(LOG, "condition_cmd blocked on-timeout, retrying in {}s", pListener->conditionRetry); + pListener->conditionPending = true; + pListener->conditionRetryAt = nowMonotonic() + pListener->conditionRetry; + } else { + Debug::log(LOG, "condition_cmd blocked on-timeout, no retry configured"); + } + return; + } + } + Debug::log(LOG, "Running {}", pListener->onTimeout); + pListener->onTimeoutFired = true; spawn(pListener->onTimeout); } void CHypridle::onResumed(SIdleListener* pListener) { Debug::log(LOG, "Resumed: rule {:x}", (uintptr_t)pListener); isIdled = false; - if (g_pHypridle->m_iInhibitLocks > 0 && !pListener->ignoreInhibit) { - Debug::log(LOG, "Ignoring from onResumed(), inhibit locks: {}", g_pHypridle->m_iInhibitLocks); + + // Any input means the user is no longer idle for *any* rule, so cancel all + // pending condition retries, not just this listener's. Per-listener clearing + // is insufficient: onInhibit() destroys and recreates idle notifications when + // the inhibit count drops to 0 while idle (e.g. a media player toggling the + // screensaver inhibit). That orphans a long-timeout rule's pending retry -- + // its old notification is gone, and the recreated one won't fire resume until + // a fresh full timeout elapses, which never happens if the user stays active. + // The retry then waits on conditionPending alone and fires when the condition + // finally clears, despite continuous input. Clearing all on any resume fixes it. + for (auto& l : m_sWaylandIdleState.listeners) + l.conditionPending = false; + + // If on-timeout never actually executed (was inhibited), skip on-resume too + if (!pListener->onTimeoutFired) { + Debug::log(LOG, "Skipping onResumed: onTimeout was inhibited for rule {:x}", (uintptr_t)pListener); return; } + pListener->onTimeoutFired = false; + if (pListener->onRestore.empty()) { Debug::log(LOG, "Ignoring, onRestore is empty."); return; @@ -332,6 +403,14 @@ if (!std::string{*LOCKCMD}.empty()) spawn(*LOCKCMD); + if (m_sDBUSState.screenSaverObjects.size() > 0) { + auto obj = m_sDBUSState.screenSaverObjects[0].get(); + + obj->emitSignal("ActiveChanged") + .onInterface("org.freedesktop.ScreenSaver") + .withArguments(true); + } + if (m_inhibitSleepBehavior == SLEEP_INHIBIT_LOCK_NOTIFY) uninhibitSleep(); } @@ -340,6 +419,14 @@ Debug::log(LOG, "Wayland session got unlocked"); m_isLocked = false; + if (m_sDBUSState.screenSaverObjects.size() > 0) { + auto obj = m_sDBUSState.screenSaverObjects[0].get(); + + obj->emitSignal("ActiveChanged") + .onInterface("org.freedesktop.ScreenSaver") + .withArguments(false); + } + if (m_inhibitSleepBehavior == SLEEP_INHIBIT_LOCK_NOTIFY) inhibitSleep(); @@ -371,15 +458,8 @@ return true; } -bool CHypridle::unregisterDbusInhibitCookies(const std::string& ownerID) { - const auto IT = std::remove_if(m_sDBUSState.inhibitCookies.begin(), m_sDBUSState.inhibitCookies.end(), - [&ownerID](const CHypridle::SDbusInhibitCookie& item) { return item.ownerID == ownerID; }); - - if (IT == m_sDBUSState.inhibitCookies.end()) - return false; - - m_sDBUSState.inhibitCookies.erase(IT, m_sDBUSState.inhibitCookies.end()); - return true; +size_t CHypridle::unregisterDbusInhibitCookies(const std::string& ownerID) { + return std::erase_if(m_sDBUSState.inhibitCookies, [&ownerID](const CHypridle::SDbusInhibitCookie& item) { return item.ownerID == ownerID; }); } static void handleDbusLogin(sdbus::Message msg) { @@ -461,6 +541,7 @@ static uint32_t handleDbusScreensaver(std::string app, std::string reason, uint32_t cookie, bool inhibit, const char* sender) { std::string ownerID = sender; + bool cookieFound = false; if (!inhibit) { Debug::log(TRACE, "Read uninhibit cookie: {}", cookie); @@ -471,6 +552,7 @@ app = COOKIE.app; reason = COOKIE.reason; ownerID = COOKIE.ownerID; + cookieFound = true; if (!g_pHypridle->unregisterDbusInhibitCookie(COOKIE)) Debug::log(WARN, "BUG THIS: attempted to unregister unknown cookie"); @@ -481,7 +563,7 @@ if (inhibit) g_pHypridle->onInhibit(true); - else + else if (cookieFound) g_pHypridle->onInhibit(false); static uint32_t cookieID = 1337; @@ -506,9 +588,11 @@ if (!newOwner.empty()) return; - if (g_pHypridle->unregisterDbusInhibitCookies(oldOwner)) { + size_t removed = g_pHypridle->unregisterDbusInhibitCookies(oldOwner); + if (removed > 0) { Debug::log(LOG, "App with owner {} disconnected", oldOwner); - g_pHypridle->onInhibit(false); + for (size_t i = 0; i < removed; i++) + g_pHypridle->onInhibit(false); } } @@ -558,7 +642,12 @@ }), sdbus::registerMethod("UnInhibit").implementedAs([object = obj.get()](uint32_t c) { handleDbusScreensaver("", "", c, false, object->getCurrentlyProcessedMessage().getSender()); - })) + }), + sdbus::registerMethod("GetActive").implementedAs([]() { + return g_pHypridle->m_isLocked; + }), + sdbus::registerSignal("ActiveChanged").withParameters<bool>() + ) .forInterface(sdbus::InterfaceName{"org.freedesktop.ScreenSaver"}); m_sDBUSState.screenSaverObjects.push_back(std::move(obj)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/src/core/Hypridle.hpp new/hypridle-0.1.8/src/core/Hypridle.hpp --- old/hypridle-0.1.7/src/core/Hypridle.hpp 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/src/core/Hypridle.hpp 2026-07-26 16:44:59.000000000 +0200 @@ -17,10 +17,15 @@ CHypridle(); struct SIdleListener { - SP<CCExtIdleNotificationV1> notification = nullptr; - std::string onTimeout = ""; - std::string onRestore = ""; - bool ignoreInhibit = false; + SP<CCExtIdleNotificationV1> notification = nullptr; + std::string onTimeout = ""; + std::string onRestore = ""; + bool ignoreInhibit = false; + bool onTimeoutFired = false; + std::string conditionCmd = ""; + int64_t conditionRetry = 0; + bool conditionPending = false; + int64_t conditionRetryAt = 0; }; struct SDbusInhibitCookie { @@ -44,7 +49,7 @@ SDbusInhibitCookie getDbusInhibitCookie(uint32_t cookie); void registerDbusInhibitCookie(SDbusInhibitCookie& cookie); bool unregisterDbusInhibitCookie(const SDbusInhibitCookie& cookie); - bool unregisterDbusInhibitCookies(const std::string& ownerID); + size_t unregisterDbusInhibitCookies(const std::string& ownerID); void handleInhibitOnDbusSleep(bool toSleep); void inhibitSleep(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hypridle-0.1.7/src/main.cpp new/hypridle-0.1.8/src/main.cpp --- old/hypridle-0.1.7/src/main.cpp 2025-08-27 12:40:01.000000000 +0200 +++ new/hypridle-0.1.8/src/main.cpp 2026-07-26 16:44:59.000000000 +0200 @@ -2,6 +2,7 @@ #include "config/ConfigManager.hpp" #include "core/Hypridle.hpp" #include "helpers/Log.hpp" +#include <memory> int main(int argc, char** argv, char** envp) { std::string configPath; @@ -40,29 +41,33 @@ else if (arg == "--help" || arg == "-h") { Debug::log(NONE, - "Usage: hypridle [options]\n" - "Options:\n" - " -v, --verbose Enable verbose logging\n" - " -q, --quiet Suppress all output except errors\n" - " -V, --version Show version information\n" - " -c, --config <path> Specify a custom config file path\n" - " -h, --help Show this help message" - ); + "Usage: hypridle [options]\n" + "Options:\n" + " -v, --verbose Enable verbose logging\n" + " -q, --quiet Suppress all output except errors\n" + " -V, --version Show version information\n" + " -c, --config <path> Specify a custom config file path\n" + " -h, --help Show this help message"); return 0; } } - try { - g_pConfigManager = std::make_unique<CConfigManager>(configPath); - g_pConfigManager->init(); - } catch (const char* err) { - Debug::log(CRIT, "ConfigManager threw: {}", err); - std::string strerr = err; - if (strerr.contains("File does not exist")) - Debug::log(NONE, " Make sure you have a config."); + g_pConfigManager = std::make_unique<CConfigManager>(configPath); + + if (g_pConfigManager->configCurrentPath.empty()) { + if (!configPath.empty()) { + Debug::log(CRIT, "ConfigManager: Specified file not found: {}\n", configPath); + } else { + Debug::log(CRIT, "ConfigManager: No hypridle.conf file found in:"); + Debug::log(NONE, " $XDG_CONFIG_HOME/hypr/, ~/.config/hypr/, [XDG_CONFIG_DIRS]/hypr/, /etc/xdg/hypr/\n"); + Debug::log(NONE, "Create a config or specify one manually:"); + Debug::log(NONE, " hypridle -c /path/to/conf"); + } return 1; } + g_pConfigManager->init(); + g_pHypridle = std::make_unique<CHypridle>(); g_pHypridle->run(); ++++++ hypridle.obsinfo ++++++ --- /var/tmp/diff_new_pack.gTh1vu/_old 2026-08-01 18:34:32.382590323 +0200 +++ /var/tmp/diff_new_pack.gTh1vu/_new 2026-08-01 18:34:32.386590461 +0200 @@ -1,5 +1,5 @@ name: hypridle -version: 0.1.7 -mtime: 1756291201 -commit: 5430b73ddf148651bcf35fa39ed4d757c7534028 +version: 0.1.8 +mtime: 1785077099 +commit: e5c01af0842bd66617f7004568df9406111d6e80
