pax_global_header00006660000000000000000000000064152314165530014517gustar00rootroot0000000000000052 comment=e5c01af0842bd66617f7004568df9406111d6e80 hyprwm-hypridle-e5c01af/000077500000000000000000000000001523141655300153515ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/.clang-format000066400000000000000000000034161523141655300177300ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-hypridle-e5c01af/.clang-tidy000066400000000000000000000072721523141655300174150ustar00rootroot00000000000000WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' FormatStyle: 'file' Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declaration-namespace, -bugprone-forward-declaration-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, -bugprone-assignment-in-if-condition, concurrency-*, -concurrency-mt-unsafe, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-init-variables, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, google-global-names-in-headers, -google-readability-casting, google-runtime-operator, misc-*, -misc-unused-parameters, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -misc-use-anonymous-namespace, -misc-const-correctness, modernize-*, -modernize-return-braced-init-list, -modernize-use-trailing-return-type, -modernize-use-using, -modernize-use-override, -modernize-avoid-c-arrays, -modernize-macro-to-enum, -modernize-loop-convert, -modernize-use-nodiscard, -modernize-pass-by-value, -modernize-use-auto, performance-*, -performance-avoid-endl, -performance-unnecessary-value-param, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-magic-numbers, -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, -readability-else-after-return, -readability-container-data-pointer, -readability-implicit-bool-conversion, -readability-avoid-nested-conditional-operator, -readability-redundant-member-init, -readability-redundant-string-init, -readability-avoid-const-params-in-decls, -readability-named-parameter, -readability-convert-member-functions-to-static, -readability-qualified-auto, -readability-make-member-function-const, -readability-isolate-declaration, -readability-inconsistent-declaration-parameter-name, -clang-diagnostic-error, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true performance-inefficient-string-concatenation.StrictMode: true readability-braces-around-statements.ShortStatementLines: 0 readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ClassIgnoredRegexp: I.* readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? readability-identifier-naming.EnumCase: CamelCase readability-identifier-naming.EnumPrefix: e readability-identifier-naming.EnumConstantCase: UPPER_CASE readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.NamespaceCase: CamelCase readability-identifier-naming.NamespacePrefix: N readability-identifier-naming.StructPrefix: S readability-identifier-naming.StructCase: CamelCase hyprwm-hypridle-e5c01af/.github/000077500000000000000000000000001523141655300167115ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/.github/workflows/000077500000000000000000000000001523141655300207465ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/.github/workflows/nix.yml000066400000000000000000000004611523141655300222700ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: 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 hyprwm-hypridle-e5c01af/.gitignore000066400000000000000000000001131523141655300173340ustar00rootroot00000000000000.vscode/ build/ protocols/ .clangd/ .direnv/ .cache/ compile_commands.json hyprwm-hypridle-e5c01af/CMakeLists.txt000066400000000000000000000067601523141655300201220ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} VERSION) project( hypridle DESCRIPTION "An idle management daemon for Hyprland" VERSION ${VERSION}) set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hypridle in Debug with CMake") add_compile_definitions(HYPRLAND_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring hypridle in Release with CMake") endif() add_compile_definitions(HYPRIDLE_VERSION="${VERSION}") include_directories(. "protocols/") include(GNUInstallDirs) # configure set(CMAKE_CXX_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) add_compile_options(-Wall -Wextra -Wuseless-cast -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers) configure_file(systemd/hypridle.service.in systemd/hypridle.service @ONLY) # dependencies message(STATUS "Checking deps...") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) find_package(hyprwayland-scanner 0.4.4 REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprlang>=0.6.0 hyprutils>=0.2.0 sdbus-c++>=0.2.0) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(hypridle ${SRCFILES}) target_link_libraries(hypridle PRIVATE rt Threads::Threads PkgConfig::deps) # protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) message(STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}") pkg_check_modules(hyprland_protocols_dep REQUIRED IMPORTED_TARGET hyprland-protocols>=0.6.0) pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir) message(STATUS "Found hyprland-protocols at ${HYPRLAND_PROTOCOLS}") function(protocolnew protoPath protoName external) if(external) set(path ${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() message(STATUS "Full proto path: ${path}") add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hypridle PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) endfunction() function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(hypridle PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so protocolwayland() protocolnew("staging/ext-idle-notify" "ext-idle-notify-v1" false) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-lock-notify-v1" true) # Installation install(TARGETS hypridle) install(FILES ${CMAKE_BINARY_DIR}/systemd/hypridle.service DESTINATION "lib/systemd/user") install( FILES ${CMAKE_SOURCE_DIR}/assets/example.conf DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr RENAME hypridle.conf) hyprwm-hypridle-e5c01af/LICENSE000066400000000000000000000027371523141655300163670ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2024, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hyprwm-hypridle-e5c01af/README.md000066400000000000000000000050461523141655300166350ustar00rootroot00000000000000# hypridle Hyprland's idle daemon ## Features - based on the `ext-idle-notify-v1` wayland protocol - support for dbus' loginctl commands (lock / unlock / before-sleep) - support for dbus' inhibit (used by e.g. firefox / steam) ## Configuration Configuration is done via `~/.config/hypr/hypridle.conf` in the standard hyprland syntax. ```ini general { lock_cmd = notify-send "lock!" # dbus/sysd lock command (loginctl lock-session) unlock_cmd = notify-send "unlock!" # same as above, but unlock before_sleep_cmd = notify-send "Zzz" # command ran before sleep after_sleep_cmd = notify-send "Awake!" # command ran after sleep ignore_dbus_inhibit = false # whether to ignore dbus-sent idle-inhibit requests (used by e.g. firefox or steam) ignore_systemd_inhibit = false # whether to ignore systemd-inhibit --what=idle inhibitors } listener { 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) } ``` You can add as many listeners as you please. Omitting `on-timeout` or `on-resume` (or leaving them empty) will make those events ignored. ## Dependencies - wayland - wayland-protocols - hyprland-protocols - hyprlang >= 0.4.0 - sdbus-c++ - hyprwayland-scanner ## Building & Installation ### Building: ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` ``` ### Installation: ```sh sudo cmake --install build ``` ### Usage: Hypridle should ideally be launched after logging in. This can be done by your compositor or by systemd. For example, for Hyprland, use the following in your `hyprland.conf`. ```hyprlang exec-once = hypridle ``` If, instead, you want to have systemd do this for you, you'll just need to enable the service using ```sh systemctl --user enable --now hypridle.service ``` ## Flags ``` -c , --config : specify a config path, by default set to ${XDG_CONFIG_HOME}/hypr/hypridle.conf -q, --quiet -v, --verbose ``` hyprwm-hypridle-e5c01af/VERSION000066400000000000000000000000061523141655300164150ustar00rootroot000000000000000.1.8 hyprwm-hypridle-e5c01af/assets/000077500000000000000000000000001523141655300166535ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/assets/example.conf000066400000000000000000000032011523141655300211510ustar00rootroot00000000000000# sample hypridle.conf # 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 'hl.dsp.dpms({action = "on"})' # turn on screen w/o key press after resume. } listener { timeout = 150 # 2.5min. on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor. on-resume = brightnessctl -r # monitor backlight restore. } # turn off keyboard backlight, comment out this section if you dont have a keyboard backlight. listener { timeout = 150 # 2.5min. on-timeout = brightnessctl -sd rgb:kbd_backlight set 0 # turn off keyboard backlight. on-resume = brightnessctl -rd rgb:kbd_backlight # turn on keyboard backlight. } listener { timeout = 300 # 5min. on-timeout = loginctl lock-session # lock screen when timeout has passed. } listener { 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 { timeout = 1800 # 30min. on-timeout = systemctl suspend # suspend pc. } hyprwm-hypridle-e5c01af/flake.lock000066400000000000000000000066231523141655300173140ustar00rootroot00000000000000{ "nodes": { "hyprland-protocols": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772460177, "narHash": "sha256-/6G/MsPvtn7bc4Y32pserBT/Z4SUUdBd4XYJpOEKVR4=", "owner": "hyprwm", "repo": "hyprland-protocols", "rev": "1cb6db5fd6bb8aee419f4457402fa18293ace917", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-protocols", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459629, "narHash": "sha256-/iwvNUYShmmnwmz/czEUh6+0eF5vCMv0xtDW0STPIuM=", "owner": "hyprwm", "repo": "hyprlang", "rev": "7615ee388de18239a4ab1400946f3d0e498a8186", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459870, "narHash": "sha256-xxkK2Cvqxpf/4UGcJ/TyCwrvmiNWsKsJfFzHMp2bxis=", "owner": "hyprwm", "repo": "hyprutils", "rev": "e63f3a79334dec49f8eb1691f66f18115df04085", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1772459835, "narHash": "sha256-978jRz/y/9TKmZb/qD4lEYHCQGHpEXGqy+8X2lFZsak=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "0a692d4a645165eebd65f109146b8861e3a925e7", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1772198003, "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "owner": "NixOS", "repo": "nixpkgs", "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprland-protocols": "hyprland-protocols", "hyprlang": "hyprlang", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", "systems": "systems" } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } } }, "root": "root", "version": 7 } hyprwm-hypridle-e5c01af/flake.nix000066400000000000000000000034251523141655300171570ustar00rootroot00000000000000{ description = "Hyprland's idle daemon"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprlang = { url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprland-protocols = { url = "github:hyprwm/hyprland-protocols"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; }; 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; }); 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}); formatter = eachSystem (system: pkgsFor.${system}.nixfmt-tree); }; } hyprwm-hypridle-e5c01af/nix/000077500000000000000000000000001523141655300161475ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/nix/default.nix000066400000000000000000000013631523141655300203160ustar00rootroot00000000000000{ cmake, hyprland-protocols, hyprlang, hyprutils, hyprwayland-scanner, lib, pkg-config, sdbus-cpp_2, stdenv, systemd, wayland, wayland-protocols, wayland-scanner, version ? "git", }: stdenv.mkDerivation { pname = "hypridle"; inherit version; src = ../.; nativeBuildInputs = [ cmake hyprwayland-scanner pkg-config wayland-scanner ]; buildInputs = [ hyprland-protocols hyprlang hyprutils sdbus-cpp_2 systemd wayland wayland-protocols ]; meta = { homepage = "https://github.com/hyprwm/hypridle"; description = "An idle management daemon for Hyprland"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; mainProgram = "hypridle"; }; } hyprwm-hypridle-e5c01af/nix/overlays.nix000066400000000000000000000016341523141655300205370ustar00rootroot00000000000000{ lib, inputs, 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 { default = inputs.self.overlays.hypridle; hypridle-with-deps = lib.composeManyExtensions [ inputs.hyprland-protocols.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default self.overlays.hypridle ]; 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; }; }; } hyprwm-hypridle-e5c01af/src/000077500000000000000000000000001523141655300161405ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/src/config/000077500000000000000000000000001523141655300174055ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/src/config/ConfigManager.cpp000066400000000000000000000166751523141655300226300ustar00rootroot00000000000000#include "ConfigManager.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include #include #include #include #include static std::expected 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 const auto paths = Hyprutils::Path::findConfig("hypridle"); if (paths.first.has_value()) return paths.first.value(); return std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory)); } CConfigManager::CConfigManager(std::string 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) { const std::string VALUE = v; const std::string COMMAND = c; const auto RESULT = g_pConfigManager->handleSource(COMMAND, VALUE); Hyprlang::CParseResult result; if (RESULT.has_value()) result.setError(RESULT.value().c_str()); return result; } void CConfigManager::init() { m_config.addSpecialCategory("listener", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("listener", "timeout", Hyprlang::INT{-1}); 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{""}); m_config.addConfigValue("general:on_lock_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:on_unlock_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:before_sleep_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:after_sleep_cmd", Hyprlang::STRING{""}); m_config.addConfigValue("general:ignore_dbus_inhibit", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_systemd_inhibit", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_wayland_inhibit", Hyprlang::INT{0}); m_config.addConfigValue("general:inhibit_sleep", Hyprlang::INT{2}); // track the file in the circular dependency chain alreadyIncludedSourceFiles.insert(std::filesystem::canonical(configHeadPath)); m_config.registerHandler(&::handleSource, "source", {.allowFlags = false}); m_config.commence(); auto result = m_config.parse(); if (result.error) Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError()); result = postParse(); if (result.error) Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError()); } Hyprlang::CParseResult CConfigManager::postParse() { const auto KEYS = m_config.listKeysForSpecialCategory("listener"); Hyprlang::CParseResult result; if (KEYS.empty()) { result.setError("No rules configured"); return result; } for (auto& k : KEYS) { STimeoutRule rule; Hyprlang::INT timeout = std::any_cast(m_config.getSpecialConfigValue("listener", "timeout", k.c_str())); rule.timeout = timeout; rule.onTimeout = std::any_cast(m_config.getSpecialConfigValue("listener", "on-timeout", k.c_str())); 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"); continue; } m_vRules.emplace_back(rule); } for (auto& r : m_vRules) { 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; } std::vector CConfigManager::getRules() { return m_vRules; } std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { if (rawpath.length() < 2) return "source path " + rawpath + " bogus!"; std::unique_ptr glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; memset(glob_buf.get(), 0, sizeof(glob_t)); const auto CURRENTDIR = std::filesystem::path(configCurrentPath).parent_path().string(); if (auto r = glob(absolutePath(rawpath, CURRENTDIR).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); Debug::log(ERR, "{}", err); return err; } for (size_t i = 0; i < glob_buf->gl_pathc; i++) { const auto PATH = absolutePath(glob_buf->gl_pathv[i], CURRENTDIR); if (PATH.empty() || PATH == configCurrentPath) { Debug::log(WARN, "source= skipping invalid path"); continue; } if (std::find(alreadyIncludedSourceFiles.begin(), alreadyIncludedSourceFiles.end(), PATH) != alreadyIncludedSourceFiles.end()) { Debug::log(WARN, "source= skipping already included source file {} to prevent circular dependency", PATH); continue; } if (!std::filesystem::is_regular_file(PATH)) { if (std::filesystem::exists(PATH)) { Debug::log(WARN, "source= skipping non-file {}", PATH); continue; } Debug::log(ERR, "source= file doesnt exist"); return "source file " + PATH + " doesn't exist!"; } // track the file in the circular dependency chain alreadyIncludedSourceFiles.insert(PATH); // allow for nested config parsing auto backupConfigPath = configCurrentPath; configCurrentPath = PATH; m_config.parseFile(PATH.c_str()); configCurrentPath = backupConfigPath; } return {}; } hyprwm-hypridle-e5c01af/src/config/ConfigManager.hpp000066400000000000000000000021471523141655300226220ustar00rootroot00000000000000#pragma once #include "../helpers/Log.hpp" #include #include #include #include class CConfigManager { public: CConfigManager(std::string configPath); void init(); struct STimeoutRule { uint64_t timeout = 0; std::string onTimeout = ""; std::string onResume = ""; bool ignoreInhibit = false; std::string conditionCmd = ""; int64_t conditionRetry = 0; }; std::vector getRules(); std::optional handleSource(const std::string&, const std::string&); std::string configCurrentPath, configHeadPath; std::set alreadyIncludedSourceFiles; template Hyprlang::CSimpleConfigValue getValue(const std::string& name) { return Hyprlang::CSimpleConfigValue(&m_config, name.c_str()); } private: Hyprlang::CConfig m_config; std::vector m_vRules; Hyprlang::CParseResult postParse(); }; inline std::unique_ptr g_pConfigManager; hyprwm-hypridle-e5c01af/src/core/000077500000000000000000000000001523141655300170705ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/src/core/Hypridle.cpp000066400000000000000000000722701523141655300213640ustar00rootroot00000000000000 #include "Hypridle.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "csignal" #include #include #include #include #include #include #include #include #include CHypridle::CHypridle() { m_sWaylandState.display = wl_display_connect(nullptr); if (!m_sWaylandState.display) { Debug::log(CRIT, "Couldn't connect to a wayland compositor"); exit(1); } } void CHypridle::run() { m_sWaylandState.registry = makeShared((wl_proxy*)wl_display_get_registry(m_sWaylandState.display)); m_sWaylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) { const std::string IFACE = interface; Debug::log(LOG, " | got iface: {} v{}", IFACE, version); if (IFACE == ext_idle_notifier_v1_interface.name) { m_sWaylandIdleState.notifier = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &ext_idle_notifier_v1_interface, version)); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == hyprland_lock_notifier_v1_interface.name) { m_sWaylandState.lockNotifier = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &hyprland_lock_notifier_v1_interface, version)); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } else if (IFACE == wl_seat_interface.name) { if (m_sWaylandState.seat) { Debug::log(WARN, "Hypridle does not support multi-seat configurations. Only binding to the first seat."); return; } m_sWaylandState.seat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_seat_interface, version)); Debug::log(LOG, " > Bound to {} v{}", IFACE, version); } }); m_sWaylandState.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t name) { Debug::log(LOG, " | removed iface {}", name); }); wl_display_roundtrip(m_sWaylandState.display); if (!m_sWaylandIdleState.notifier) { Debug::log(CRIT, "Couldn't bind to ext-idle-notifier-v1, does your compositor support it?"); exit(1); } static const auto IGNOREWAYLANDINHIBIT = g_pConfigManager->getValue("general:ignore_wayland_inhibit"); const auto RULES = g_pConfigManager->getRules(); m_sWaylandIdleState.listeners.resize(RULES.size()); Debug::log(LOG, "found {} rules", RULES.size()); 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.conditionCmd = r.conditionCmd; l.conditionRetry = r.conditionRetry; if (*IGNOREWAYLANDINHIBIT || r.ignoreInhibit) l.notification = makeShared(m_sWaylandIdleState.notifier->sendGetInputIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource())); else l.notification = makeShared(m_sWaylandIdleState.notifier->sendGetIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource())); l.notification->setData(&m_sWaylandIdleState.listeners[i]); l.notification->setIdled([this](CCExtIdleNotificationV1* n) { onIdled((CHypridle::SIdleListener*)n->data()); }); l.notification->setResumed([this](CCExtIdleNotificationV1* n) { onResumed((CHypridle::SIdleListener*)n->data()); }); } wl_display_roundtrip(m_sWaylandState.display); if (m_sWaylandState.lockNotifier) { m_sWaylandState.lockNotification = makeShared(m_sWaylandState.lockNotifier->sendGetLockNotification()); m_sWaylandState.lockNotification->setLocked([this](CCHyprlandLockNotificationV1* n) { onLocked(); }); m_sWaylandState.lockNotification->setUnlocked([this](CCHyprlandLockNotificationV1* n) { onUnlocked(); }); } Debug::log(LOG, "wayland done, registering dbus"); try { m_sDBUSState.connection = sdbus::createSystemBusConnection(); } catch (std::exception& e) { Debug::log(CRIT, "Couldn't create the dbus connection ({})", e.what()); exit(1); } if (!m_sWaylandState.lockNotifier) Debug::log(WARN, "Compositor is missing hyprland-lock-notify-v1!\n" "general:inhibit_sleep=3, general:on_lock_cmd and general:on_unlock_cmd will not work."); static const auto INHIBIT = g_pConfigManager->getValue("general:inhibit_sleep"); static const auto SLEEPCMD = g_pConfigManager->getValue("general:before_sleep_cmd"); static const auto LOCKCMD = g_pConfigManager->getValue("general:lock_cmd"); switch (*INHIBIT) { case 0: // disabled m_inhibitSleepBehavior = SLEEP_INHIBIT_NONE; break; case 1: // enabled m_inhibitSleepBehavior = SLEEP_INHIBIT_NORMAL; break; case 2: { // auto (enable, but wait until locked if before_sleep_cmd contains hyprlock, or loginctl lock-session and lock_cmd contains hyprlock.) if (m_sWaylandState.lockNotifier && std::string{*SLEEPCMD}.contains("hyprlock")) m_inhibitSleepBehavior = SLEEP_INHIBIT_LOCK_NOTIFY; else if (m_sWaylandState.lockNotifier && std::string{*LOCKCMD}.contains("hyprlock") && std::string{*SLEEPCMD}.contains("lock-session")) m_inhibitSleepBehavior = SLEEP_INHIBIT_LOCK_NOTIFY; else m_inhibitSleepBehavior = SLEEP_INHIBIT_NORMAL; } break; case 3: // wait until locked if (m_sWaylandState.lockNotifier) m_inhibitSleepBehavior = SLEEP_INHIBIT_LOCK_NOTIFY; break; default: Debug::log(ERR, "Invalid inhibit_sleep value: {}", *INHIBIT); break; } switch (m_inhibitSleepBehavior) { case SLEEP_INHIBIT_NONE: Debug::log(LOG, "Sleep inhibition disabled"); break; case SLEEP_INHIBIT_NORMAL: Debug::log(LOG, "Sleep inhibition enabled"); break; case SLEEP_INHIBIT_LOCK_NOTIFY: Debug::log(LOG, "Sleep inhibition enabled - inhibiting until the wayland session gets locked"); break; } setupDBUS(); if (m_inhibitSleepBehavior != SLEEP_INHIBIT_NONE) inhibitSleep(); 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); 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; pollfd pollfds[] = { { .fd = m_sDBUSState.connection->getEventLoopPollData().fd, .events = POLLIN, }, { .fd = wl_display_get_fd(m_sWaylandState.display), .events = POLLIN, }, { .fd = m_sDBUSState.screenSaverServiceConnection ? m_sDBUSState.screenSaverServiceConnection->getEventLoopPollData().fd : 0, .events = POLLIN, }, }; std::thread pollThr([this, &pollfds, &pollfdsCount]() { while (1) { int ret = poll(pollfds, pollfdsCount, 5000 /* 5 seconds, reasonable. It's because we might need to terminate */); if (ret < 0) { Debug::log(CRIT, "[core] Polling fds failed with {}", errno); m_bTerminate = true; exit(1); } for (size_t i = 0; i < pollfdsCount; ++i) { if (pollfds[i].revents & POLLHUP) { Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i); m_bTerminate = true; exit(1); } } if (m_bTerminate) break; if (ret != 0) { Debug::log(TRACE, "[core] got poll event"); std::lock_guard lg(m_sEventLoopInternals.loopRequestMutex); m_sEventLoopInternals.shouldProcess = true; m_sEventLoopInternals.loopSignal.notify_all(); } } }); while (1) { // dbus events // wait for being awakened m_sEventLoopInternals.loopRequestMutex.unlock(); // unlock, we are ready to take events 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_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 if (m_bTerminate) break; m_sEventLoopInternals.shouldProcess = false; std::lock_guard lg(m_sEventLoopInternals.eventLock); if (pollfds[0].revents & POLLIN /* dbus */) { Debug::log(TRACE, "got dbus event"); while (m_sDBUSState.connection->processPendingEvent()) { ; } } if (pollfds[1].revents & POLLIN /* wl */) { Debug::log(TRACE, "got wl event"); wl_display_flush(m_sWaylandState.display); if (wl_display_prepare_read(m_sWaylandState.display) == 0) { wl_display_read_events(m_sWaylandState.display); wl_display_dispatch_pending(m_sWaylandState.display); } else { wl_display_dispatch(m_sWaylandState.display); } } if (pollfdsCount > 2 && pollfds[2].revents & POLLIN /* dbus2 */) { Debug::log(TRACE, "got dbus event"); while (m_sDBUSState.screenSaverServiceConnection->processPendingEvent()) { ; } } // finalize wayland dispatching. Dispatch pending on the queue int ret = 0; do { 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"); } static void spawn(const std::string& args) { Debug::log(LOG, "Executing {}", args); Hyprutils::OS::CProcess proc("/bin/sh", {"-c", args}); if (!proc.runAsync()) { Debug::log(ERR, "Failed run \"{}\"", args); return; } Debug::log(LOG, "Process Created with pid {}", proc.pid()); } void CHypridle::onIdled(SIdleListener* pListener) { Debug::log(LOG, "Idled: rule {:x}", (uintptr_t)pListener); isIdled = true; if (g_pHypridle->m_iInhibitLocks > 0 && !pListener->ignoreInhibit) { Debug::log(LOG, "Ignoring from onIdled(), inhibit locks: {}", g_pHypridle->m_iInhibitLocks); return; } if (pListener->onTimeout.empty()) { Debug::log(LOG, "Ignoring, onTimeout is empty."); 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; // 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; } Debug::log(LOG, "Running {}", pListener->onRestore); spawn(pListener->onRestore); } void CHypridle::onInhibit(bool lock) { m_iInhibitLocks += lock ? 1 : -1; if (m_iInhibitLocks < 0) { Debug::log(WARN, "BUG THIS: inhibit locks < 0: {}", m_iInhibitLocks); m_iInhibitLocks = 0; } if (m_iInhibitLocks == 0 && isIdled) { static const auto IGNOREWAYLANDINHIBIT = g_pConfigManager->getValue("general:ignore_wayland_inhibit"); const auto RULES = g_pConfigManager->getRules(); for (size_t i = 0; i < RULES.size(); ++i) { auto& l = m_sWaylandIdleState.listeners[i]; const auto& r = RULES[i]; l.notification->sendDestroy(); if (*IGNOREWAYLANDINHIBIT || r.ignoreInhibit) l.notification = makeShared(m_sWaylandIdleState.notifier->sendGetInputIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource())); else l.notification = makeShared(m_sWaylandIdleState.notifier->sendGetIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource())); l.notification->setData(&m_sWaylandIdleState.listeners[i]); l.notification->setIdled([this](CCExtIdleNotificationV1* n) { onIdled((CHypridle::SIdleListener*)n->data()); }); l.notification->setResumed([this](CCExtIdleNotificationV1* n) { onResumed((CHypridle::SIdleListener*)n->data()); }); } } Debug::log(LOG, "Inhibit locks: {}", m_iInhibitLocks); } void CHypridle::onLocked() { Debug::log(LOG, "Wayland session got locked"); m_isLocked = true; static const auto LOCKCMD = g_pConfigManager->getValue("general:on_lock_cmd"); 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(); } void CHypridle::onUnlocked() { 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(); static const auto UNLOCKCMD = g_pConfigManager->getValue("general:on_unlock_cmd"); if (!std::string{*UNLOCKCMD}.empty()) spawn(*UNLOCKCMD); } CHypridle::SDbusInhibitCookie CHypridle::getDbusInhibitCookie(uint32_t cookie) { for (auto& c : m_sDBUSState.inhibitCookies) { if (c.cookie == cookie) return c; } return {}; } void CHypridle::registerDbusInhibitCookie(CHypridle::SDbusInhibitCookie& cookie) { m_sDBUSState.inhibitCookies.push_back(cookie); } bool CHypridle::unregisterDbusInhibitCookie(const CHypridle::SDbusInhibitCookie& cookie) { const auto IT = std::ranges::find_if(m_sDBUSState.inhibitCookies, [&cookie](const CHypridle::SDbusInhibitCookie& item) { return item.cookie == cookie.cookie; }); if (IT == m_sDBUSState.inhibitCookies.end()) return false; m_sDBUSState.inhibitCookies.erase(IT); 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) { // lock & unlock static const auto LOCKCMD = g_pConfigManager->getValue("general:lock_cmd"); static const auto UNLOCKCMD = g_pConfigManager->getValue("general:unlock_cmd"); Debug::log(LOG, "Got dbus .Session"); const std::string MEMBER = msg.getMemberName(); if (MEMBER == "Lock") { Debug::log(LOG, "Got Lock from dbus"); if (!std::string{*LOCKCMD}.empty()) { Debug::log(LOG, "Locking with {}", *LOCKCMD); spawn(*LOCKCMD); } } else if (MEMBER == "Unlock") { Debug::log(LOG, "Got Unlock from dbus"); if (!std::string{*UNLOCKCMD}.empty()) { Debug::log(LOG, "Unlocking with {}", *UNLOCKCMD); spawn(*UNLOCKCMD); } } } static void handleDbusSleep(sdbus::Message msg) { const std::string MEMBER = msg.getMemberName(); if (MEMBER != "PrepareForSleep") return; bool toSleep = true; msg >> toSleep; static const auto SLEEPCMD = g_pConfigManager->getValue("general:before_sleep_cmd"); static const auto AFTERSLEEPCMD = g_pConfigManager->getValue("general:after_sleep_cmd"); Debug::log(LOG, "Got PrepareForSleep from dbus with sleep {}", toSleep); std::string cmd = toSleep ? *SLEEPCMD : *AFTERSLEEPCMD; if (!toSleep) g_pHypridle->handleInhibitOnDbusSleep(toSleep); if (!cmd.empty()) spawn(cmd); if (toSleep) g_pHypridle->handleInhibitOnDbusSleep(toSleep); } static void handleDbusBlockInhibits(const std::string& inhibits) { static auto inhibited = false; // BlockInhibited is a colon separated list of inhibit types. Wrapping in additional colons allows for easier checking if there are active inhibits we are interested in auto inhibits_ = ":" + inhibits + ":"; if (inhibits_.contains(":idle:")) { if (!inhibited) { inhibited = true; Debug::log(LOG, "systemd idle inhibit active"); g_pHypridle->onInhibit(true); } } else if (inhibited) { inhibited = false; Debug::log(LOG, "systemd idle inhibit inactive"); g_pHypridle->onInhibit(false); } } static void handleDbusBlockInhibitsPropertyChanged(sdbus::Message msg) { std::string interface; std::map changedProperties; msg >> interface >> changedProperties; if (changedProperties.contains("BlockInhibited")) { handleDbusBlockInhibits(changedProperties["BlockInhibited"].get()); } } 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); const auto COOKIE = g_pHypridle->getDbusInhibitCookie(cookie); if (COOKIE.cookie == 0) { Debug::log(WARN, "No cookie in uninhibit"); } else { 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"); } } Debug::log(LOG, "ScreenSaver inhibit: {} dbus message from {} (owner: {}) with content {}", inhibit, app, ownerID, reason); if (inhibit) g_pHypridle->onInhibit(true); else if (cookieFound) g_pHypridle->onInhibit(false); static uint32_t cookieID = 1337; if (inhibit) { auto cookie = CHypridle::SDbusInhibitCookie{.cookie = cookieID, .app = app, .reason = reason, .ownerID = ownerID}; Debug::log(LOG, "Cookie {} sent", cookieID); g_pHypridle->registerDbusInhibitCookie(cookie); return cookieID++; } return 0; } static void handleDbusNameOwnerChanged(sdbus::Message msg) { std::string name, oldOwner, newOwner; msg >> name >> oldOwner >> newOwner; if (!newOwner.empty()) return; size_t removed = g_pHypridle->unregisterDbusInhibitCookies(oldOwner); if (removed > 0) { Debug::log(LOG, "App with owner {} disconnected", oldOwner); for (size_t i = 0; i < removed; i++) g_pHypridle->onInhibit(false); } } void CHypridle::setupDBUS() { static const auto IGNOREDBUSINHIBIT = g_pConfigManager->getValue("general:ignore_dbus_inhibit"); static const auto IGNORESYSTEMDINHIBIT = g_pConfigManager->getValue("general:ignore_systemd_inhibit"); auto systemConnection = sdbus::createSystemBusConnection(); auto proxy = sdbus::createProxy(*systemConnection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"}); sdbus::ObjectPath path; try { proxy->callMethod("GetSession").onInterface("org.freedesktop.login1.Manager").withArguments(std::string{"auto"}).storeResultsTo(path); m_sDBUSState.connection->addMatch("type='signal',path='" + path + "',interface='org.freedesktop.login1.Session'", ::handleDbusLogin); m_sDBUSState.connection->addMatch("type='signal',path='/org/freedesktop/login1',interface='org.freedesktop.login1.Manager'", ::handleDbusSleep); m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"}); } catch (std::exception& e) { Debug::log(WARN, "Couldn't connect to logind service ({})", e.what()); } Debug::log(LOG, "Using dbus path {}", path.c_str()); if (!*IGNORESYSTEMDINHIBIT) { m_sDBUSState.connection->addMatch("type='signal',path='/org/freedesktop/login1',interface='org.freedesktop.DBus.Properties'", ::handleDbusBlockInhibitsPropertyChanged); try { std::string value = (proxy->getProperty("BlockInhibited").onInterface("org.freedesktop.login1.Manager")).get(); handleDbusBlockInhibits(value); } catch (std::exception& e) { Debug::log(WARN, "Couldn't retrieve current systemd inhibits ({})", e.what()); } } if (!*IGNOREDBUSINHIBIT) { // attempt to register as ScreenSaver std::string paths[] = { "/org/freedesktop/ScreenSaver", "/ScreenSaver", }; try { m_sDBUSState.screenSaverServiceConnection = sdbus::createSessionBusConnection(sdbus::ServiceName{"org.freedesktop.ScreenSaver"}); for (const std::string& path : paths) { try { auto obj = sdbus::createObject(*m_sDBUSState.screenSaverServiceConnection, sdbus::ObjectPath{path}); obj->addVTable(sdbus::registerMethod("Inhibit").implementedAs([object = obj.get()](std::string s1, std::string s2) { return handleDbusScreensaver(s1, s2, 0, true, object->getCurrentlyProcessedMessage().getSender()); }), 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() ) .forInterface(sdbus::InterfaceName{"org.freedesktop.ScreenSaver"}); m_sDBUSState.screenSaverObjects.push_back(std::move(obj)); } catch (std::exception& e) { Debug::log(ERR, "Failed registering for {}, perhaps taken?\nerr: {}", path, e.what()); } } m_sDBUSState.screenSaverServiceConnection->addMatch("type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'", ::handleDbusNameOwnerChanged); } catch (sdbus::Error& e) { if (e.getName() == sdbus::Error::Name{"org.freedesktop.DBus.Error.FileExists"}) { Debug::log(ERR, "Another service is already providing the org.freedesktop.ScreenSaver interface"); Debug::log(ERR, "Is hypridle already running?"); } else Debug::log(ERR, "Failed to connect to ScreenSaver service\nerr: {}", e.what()); } } systemConnection.reset(); } void CHypridle::handleInhibitOnDbusSleep(bool toSleep) { if (m_inhibitSleepBehavior == SLEEP_INHIBIT_NONE || // m_inhibitSleepBehavior == SLEEP_INHIBIT_LOCK_NOTIFY // Sleep inhibition handled via onLocked/onUnlocked ) return; if (!toSleep) inhibitSleep(); else uninhibitSleep(); } void CHypridle::inhibitSleep() { if (!m_sDBUSState.login) { Debug::log(WARN, "Can't inhibit sleep. Dbus logind interface is not available."); return; } if (m_sDBUSState.sleepInhibitFd.isValid()) { Debug::log(WARN, "Called inhibitSleep, but previous sleep inhibitor is still active!"); m_sDBUSState.sleepInhibitFd.reset(); } auto method = m_sDBUSState.login->createMethodCall(sdbus::InterfaceName{"org.freedesktop.login1.Manager"}, sdbus::MethodName{"Inhibit"}); method << "sleep"; method << "hypridle"; method << "Hypridle wants to delay sleep until it's before_sleep handling is done."; method << "delay"; try { auto reply = m_sDBUSState.login->callMethod(method); if (!reply || !reply.isValid()) { Debug::log(ERR, "Failed to inhibit sleep"); return; } if (reply.isEmpty()) { Debug::log(ERR, "Failed to inhibit sleep, empty reply"); return; } sdbus::UnixFd fd; // This calls dup on the fd, no F_DUPFD_CLOEXEC :( // There seems to be no way to get the file descriptor out of the reply other than that. reply >> fd; // Setting the O_CLOEXEC flag does not work for some reason. Instead we make our own dupe and close the one from UnixFd. auto immidiateFD = Hyprutils::OS::CFileDescriptor(fd.release()); m_sDBUSState.sleepInhibitFd = immidiateFD.duplicate(F_DUPFD_CLOEXEC); immidiateFD.reset(); // close the fd that was opened with dup Debug::log(LOG, "Inhibited sleep with fd {}", m_sDBUSState.sleepInhibitFd.get()); } catch (const std::exception& e) { Debug::log(ERR, "Failed to inhibit sleep ({})", e.what()); } } void CHypridle::uninhibitSleep() { if (!m_sDBUSState.sleepInhibitFd.isValid()) { Debug::log(ERR, "No sleep inhibitor fd to release"); return; } Debug::log(LOG, "Releasing the sleep inhibitor!"); m_sDBUSState.sleepInhibitFd.reset(); } hyprwm-hypridle-e5c01af/src/core/Hypridle.hpp000066400000000000000000000070331523141655300213640ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "wayland.hpp" #include "ext-idle-notify-v1.hpp" #include "hyprland-lock-notify-v1.hpp" #include "../defines.hpp" class CHypridle { public: CHypridle(); struct SIdleListener { SP 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 { uint32_t cookie = 0; std::string app, reason, ownerID; }; void run(); void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name); void onIdled(SIdleListener*); void onResumed(SIdleListener*); void onInhibit(bool lock); void onLocked(); void onUnlocked(); SDbusInhibitCookie getDbusInhibitCookie(uint32_t cookie); void registerDbusInhibitCookie(SDbusInhibitCookie& cookie); bool unregisterDbusInhibitCookie(const SDbusInhibitCookie& cookie); size_t unregisterDbusInhibitCookies(const std::string& ownerID); void handleInhibitOnDbusSleep(bool toSleep); void inhibitSleep(); void uninhibitSleep(); private: void setupDBUS(); void enterEventLoop(); bool m_bTerminate = false; bool isIdled = false; bool m_isLocked = false; int64_t m_iInhibitLocks = 0; enum { SLEEP_INHIBIT_NONE, SLEEP_INHIBIT_NORMAL, SLEEP_INHIBIT_LOCK_NOTIFY, } m_inhibitSleepBehavior; struct { wl_display* display = nullptr; SP registry = nullptr; SP seat = nullptr; SP lockNotifier = nullptr; SP lockNotification = nullptr; } m_sWaylandState; struct { SP notifier = nullptr; std::vector listeners; } m_sWaylandIdleState; struct { std::unique_ptr connection; std::unique_ptr screenSaverServiceConnection; std::unique_ptr login; std::vector> screenSaverObjects; std::vector inhibitCookies; Hyprutils::OS::CFileDescriptor sleepInhibitFd; } m_sDBUSState; struct { std::condition_variable loopSignal; std::mutex loopMutex; std::atomic shouldProcess = false; std::mutex loopRequestMutex; std::mutex eventLock; } m_sEventLoopInternals; }; inline std::unique_ptr g_pHypridle; hyprwm-hypridle-e5c01af/src/defines.hpp000066400000000000000000000003361523141655300202700ustar00rootroot00000000000000#include "wayland.hpp" #include "ext-idle-notify-v1.hpp" #include "hyprland-lock-notify-v1.hpp" #include using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer hyprwm-hypridle-e5c01af/src/helpers/000077500000000000000000000000001523141655300176025ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/src/helpers/Log.hpp000066400000000000000000000045041523141655300210370ustar00rootroot00000000000000#pragma once #include #include #include enum eLogLevel { TRACE = 0, INFO, LOG, WARN, ERR, CRIT, NONE }; #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \ printf("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \ *((int*)nullptr) = 1; /* so that we crash and get a coredump */ \ } #define ASSERT(expr) RASSERT(expr, "?") namespace Debug { inline bool quiet = false; inline bool verbose = false; template void log(eLogLevel level, const std::string& fmt, Args&&... args) { if (!verbose && level == TRACE) return; if (quiet) return; if (level != NONE) { std::cout << '['; switch (level) { case TRACE: std::cout << "TRACE"; break; case INFO: std::cout << "INFO"; break; case LOG: std::cout << "LOG"; break; case WARN: std::cout << "WARN"; break; case ERR: std::cout << "ERR"; break; case CRIT: std::cout << "CRITICAL"; break; default: break; } std::cout << "] "; } std::cout << std::vformat(fmt, std::make_format_args(args...)) << std::endl; } };hyprwm-hypridle-e5c01af/src/helpers/MiscFunctions.cpp000066400000000000000000000012421523141655300230710ustar00rootroot00000000000000#include #include "MiscFunctions.hpp" std::string absolutePath(const std::string& rawpath, const std::string& currentDir) { std::filesystem::path path(rawpath); // Handling where rawpath starts with '~' if (!rawpath.empty() && rawpath[0] == '~') { static const char* const ENVHOME = getenv("HOME"); path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); } // Handling e.g. ./, ../ if (path.is_relative()) return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path); else return std::filesystem::weakly_canonical(path); }hyprwm-hypridle-e5c01af/src/helpers/MiscFunctions.hpp000066400000000000000000000001431523141655300230750ustar00rootroot00000000000000#pragma once #include std::string absolutePath(const std::string&, const std::string&); hyprwm-hypridle-e5c01af/src/main.cpp000066400000000000000000000050331523141655300175710ustar00rootroot00000000000000 #include "config/ConfigManager.hpp" #include "core/Hypridle.hpp" #include "helpers/Log.hpp" #include int main(int argc, char** argv, char** envp) { std::string configPath; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg == "--verbose" || arg == "-v") Debug::verbose = true; else if (arg == "--quiet" || arg == "-q") Debug::quiet = true; else if (arg == "--version" || arg == "-V") { Debug::log(NONE, "hypridle v{}", HYPRIDLE_VERSION); return 0; } else if (arg == "--config" || arg == "-c") { if (i + 1 >= argc) { Debug::log(NONE, "After " + arg + " you should provide a path to a config file."); return 1; } if (!configPath.empty()) { Debug::log(NONE, "Multiple config files are provided."); return 1; } configPath = argv[++i]; if (configPath[0] == '-') { // Should be fine, because of the null terminator Debug::log(NONE, "After " + arg + " you should provide a path to a config file."); return 1; } } 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 Specify a custom config file path\n" " -h, --help Show this help message"); return 0; } } g_pConfigManager = std::make_unique(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(); g_pHypridle->run(); return 0; } hyprwm-hypridle-e5c01af/systemd/000077500000000000000000000000001523141655300170415ustar00rootroot00000000000000hyprwm-hypridle-e5c01af/systemd/hypridle.service.in000066400000000000000000000005231523141655300226500ustar00rootroot00000000000000[Unit] Description=Hyprland's idle daemon Documentation=https://wiki.hyprland.org/Hypr-Ecosystem/hypridle PartOf=graphical-session.target After=graphical-session.target ConditionEnvironment=WAYLAND_DISPLAY [Service] Type=simple ExecStart=@CMAKE_INSTALL_PREFIX@/bin/hypridle Restart=on-failure [Install] WantedBy=graphical-session.target