Compare commits

...

69 commits
v0.1.0 ... main

Author SHA1 Message Date
slowsage
f3d1f3b232
core: fix dbus inhibit lock counting (#175)
Fixes #74 (root cause)

Fixes #111
2025-11-01 20:00:41 +00:00
f158b2fe92 config: fix custom config paths
fixes #172
2025-09-09 13:16:32 +01:00
5430b73ddf
version: bump to 0.1.7 2025-08-27 12:40:01 +02:00
dda793b66a
README: expand build commands 2025-07-10 18:17:31 +02:00
[Assassin]
25578b7137
core: Add --help option (#160) 2025-06-21 12:58:23 +02:00
cff17e8b52
CI/Nix: add cache-nix-action
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:24:48 +03:00
Friday
b2bc15baff nix: use gcc15
also updated dependencies
2025-06-06 01:27:05 +03:00
Chris Hixon
a282131ac7
core: add an option to ignore idle inhibition per-listener (#158)
* Add ability to ignore Wayland idle inhibitors

config: general:ignore_wayland_inhibit (bool)

If the config value general:ignore_wayland_inhibit is true, the
CCExtIdleNotifierV1 function used will be: sendGetInputIdleNotification.
This instructs the compositor (hyprland) to return all idle/resume
events, ignoring any Wayland inhibitors.

If the config value general:ignore_wayland_inhibit is false/unset,
it will use the default function sendGetIdleNotification, which obeys
Wayland inhibitors.

* Ignore idle inhibition per-listener

Add `ignore_inhibit` option (bool) to listener section of config file,
to allow ignoring idle inhibition per-listener. When set to true, all
types of inhibitors are ignored (systemd, dbus/ScreenSaver, Wayland).
Default value: false (the rule will obey inhibition)

Example:

    listener {
        timeout = 5
        on-timeout = logger 'should obey idle inhibition'
    }

    listener {
        timeout = 6
        on-timeout = logger 'should ignore idle inhibition'
        ignore_inhibit = true
    }

* Add ability to ignore Wayland idle inhibitors

config: general:ignore_wayland_inhibit (bool, default: false)

If the config value general:ignore_wayland_inhibit is true, use
sendGetInputIdleNotification to create the idle notification object.
(Wayland protocol: ext_idle_notifier_v1::get_input_idle_notification)
This instructs the compositor to return all idle/resume events,
ignoring any Wayland inhibitors.

If the config value general:ignore_wayland_inhibit is false (default),
it will use sendGetIdleNotification, which obeys Wayland inhibitors.
(Wayland protocol: ext_idle_notifier_v1::get_idle_notification)

* clang-format

* Update flake.lock

* Add newline at end of file: src/core/Hypridle.cpp
2025-06-03 19:45:20 +01:00
Chris Hixon
63d08e8e72
core: Add ability to ignore Wayland idle inhibitors (#155)
* Add ability to ignore Wayland idle inhibitors

config: general:ignore_wayland_inhibit (bool)

If the config value general:ignore_wayland_inhibit is true, the
CCExtIdleNotifierV1 function used will be: sendGetInputIdleNotification.
This instructs the compositor (hyprland) to return all idle/resume
events, ignoring any Wayland inhibitors.

If the config value general:ignore_wayland_inhibit is false/unset,
it will use the default function sendGetIdleNotification, which obeys
Wayland inhibitors.

* Add ability to ignore Wayland idle inhibitors

config: general:ignore_wayland_inhibit (bool, default: false)

If the config value general:ignore_wayland_inhibit is true, use
sendGetInputIdleNotification to create the idle notification object.
(Wayland protocol: ext_idle_notifier_v1::get_input_idle_notification)
This instructs the compositor to return all idle/resume events,
ignoring any Wayland inhibitors.

If the config value general:ignore_wayland_inhibit is false (default),
it will use sendGetIdleNotification, which obeys Wayland inhibitors.
(Wayland protocol: ext_idle_notifier_v1::get_idle_notification)

* clang-format

* Update flake.lock
2025-06-02 19:02:45 +01:00
Maximilian Seidler
4f1c165d3e
core: guard against dbus logind interface not existing and check if on_(un)lock_cmd is empty (#151)
* core: guard against dbus logind interface not existing

* core: check if on_(un)lock_cmd is empty
2025-05-19 23:03:43 +02:00
Vaxry
ecf2a3649d
README: update deps 2025-05-12 14:17:21 +02:00
martin
a0037ac40c
config: add support for source option for additional config files (#144)
* feat: support `source` option for additional config files

* fix: prevent circular dependency
2025-05-02 17:27:53 +02:00
Honkazel
b18d830276
clang-tidy: fix some errors (#143) 2025-04-22 23:23:57 +02:00
Maximilian Seidler
66d1815b5e
core: log when ScreenSaver interface is already registered (#133)
and hint that hyprilde may already be running.
2025-03-30 01:29:55 +01:00
84f9f2e127 version: bump to 0.1.6 2025-03-28 14:17:06 +00:00
davc0n
71e875e49e
assets: update example.conf (#137)
Replace current with example from wiki.
Better base for a real use case scenario.
2025-03-19 16:14:53 +01:00
Maximilian Seidler
9d97c22883
core: fix sleep delay and simplify process spawning (#127)
* core: fix sleep delay and simplify process spawning

* core: duplicate the inhibit fd with F_DUPFD_CLOEXEC

* nullptr and static

* core: use hyprutils CProcess
2025-02-28 00:16:45 +01:00
Honkazel
3e30a63b5d
core: clang-tidy and comp fixes (#126)
* clang-tidy and comp fixes

* make cookieID uint32_t

Why we should cast, when we can avoid it?
2025-02-13 17:55:17 +01:00
cf5b4dab9d
CI: remove deprecated magic-nix-cache-action 2025-02-08 23:07:58 +02:00
Maximilian Seidler
15ca902b2c
core: implement hyprlock-lock-notify-v1 functionality (#122)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-01-27 14:24:13 +01:00
33ac8cae64
flake.lock: update 2025-01-23 14:32:36 +02:00
Austin Horstman
413564cb98
nix/overlays: gcc13 -> gcc14; flake.lock: update (#116)
* nix/overlays: gcc13 -> gcc14

* flake.lock: update
2024-12-16 22:22:40 +01:00
9f23e70bb4 core: add --version 2024-11-18 19:42:36 +00:00
26780ac51f version: bump to 0.1.5 2024-11-02 15:29:47 +00:00
André Silva
4d2fb9e73e
core: handleDbusScreensaver must return uint32 (#98) 2024-10-23 11:39:20 +01:00
af2d65dcdc version: update to 0.1.4 2024-10-23 00:47:25 +01:00
Vaxry
127317f822
core: move to sdbus-cpp2 (#96)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
2024-10-23 00:45:24 +01:00
918fd78dec version: bump to 0.1.3 2024-10-21 19:08:05 +01:00
André Silva
0ed59e861c
core: release inhibit cookies on app disconnect from dbus (#93)
* core: release inhibit cookies on app disconnect from dbus

* core: clang-format
2024-10-20 23:04:37 +01:00
Eduard Tykhoniuk
cc23f97836
core: Do not crash if the last CLI parameter was -c (#92)
* fix: Do not crash if the last CLI parameter was -c

* feat: return an error if the next argument after -c is a flag

* feat: return an error if multiple config files are provided
2024-09-25 10:41:09 +01:00
André Silva
22b058b47a nix: add wayland-scanner native build input 2024-09-01 18:20:25 +03:00
BBaoVanC
a46cd0bb05
logs: Flush buffer automatically (#88) 2024-08-27 21:01:19 +02:00
BBaoVanC
01a63fcf5a
misc: Include Log.hpp in main (#87)
It is already included because of ConfigManager.hpp, but that should not
be relied on as logging is also directly used in the main function.
2024-08-27 20:41:54 +02:00
Erik Tollerud
96d51ec2a7
CMake: typo fix hyprlock->hypridle (#82) 2024-08-02 23:49:02 +03:00
f84c2d7981
assets: add example.conf 2024-08-02 22:19:47 +03:00
gnusenpai
e5366d34b5
core: Fix running without logind (#76)
* dbus: Move logind matches inside try-catch

* core: Don't exit when logind is absent

logind being missing is non-fatal
2024-07-18 22:03:02 +02:00
9163a9f318
flake.lock: update 2024-07-18 20:43:36 +03:00
ee6ca4d6c5
CMake: fmt 2024-07-18 20:43:28 +03:00
2d774e6f32
CMake, Nix: add VERSION file 2024-07-18 20:42:35 +03:00
fc4e3bd2dc
Config: use hyprutils helper (#77)
* flake.lock: update

* config: use hyprutils helper

* Nix: add hyprutils dep

flake.lock: update
2024-07-16 22:36:07 +02:00
Elvyria
7c5747b785
core: re-register all notifications when idled and inhibit was released (#72) 2024-06-22 17:00:57 +02:00
cb169c4e06
nix/hm-module: remove 2024-05-21 19:46:17 +03:00
Daniel Horton
afa6e21b88
README: Fixed getconf command in build instructions (#60)
getconf NPROCESSORS_CONF isn't a valid command. The correct command is getconf _NPROCESSORS_CONF.
2024-05-14 16:12:55 +01:00
Marcin Jaworski
a7a6b8f4f5
dbus: Separate ignore_systemd_inhibit config param, register ScreenSaver objects separately (#59)
* Don't register ScreenSaver object at all if ignore_dbus_inhibit is set. Introduce new ignore_systemd_inhibit config parameter.

* Ability to register org.freedesktop.ScreenSaver object under multiple paths. Add object under legacy /ScreenSaver path.

* Update documentation to reflect new configuration parameter.
2024-05-14 16:10:16 +01:00
Marcin Jaworski
50da2e7bf4
core: Add support for systemd-logind idle block inhibits (#57) 2024-05-13 22:22:06 +01:00
7cff4581a3 props: bump version to 0.1.2 2024-04-18 21:34:42 +01:00
alba4k
eb916db19c fix systemd service exec path 2024-04-17 09:35:26 +03:00
01772b32af
flake.lock: update 2024-04-15 23:55:20 +03:00
alba4k
72a6b38b15 fix systemd service install path 2024-04-12 21:53:15 +03:00
Aaron Blasko
dad6ac14df
Add systemd service (#45) 2024-04-12 21:16:49 +03:00
Lucas Reis
4395339a2d
core: Fix typo and check correct grandchild PID in spawn() (#34) 2024-03-11 18:14:34 +00:00
Anthony Ruhier
029f08805a
config: Add a -c|--config flag to set a config path (#25) 2024-02-29 16:19:33 +00:00
Mihai Fufezan
cc465d04d6
Nix: refactor flake 2024-02-29 15:09:56 +02:00
6ea4e04532 cmake: bump ver to 0.1.1 2024-02-28 16:05:44 +00:00
Jakub Konior
afee84925a
fix: repoll after inhibitor idled (#15)
* yep, but how to trigger it?

* stupid yet works

* forgot about lock
2024-02-27 21:22:15 +00:00
Jan Beich
790988d116
core: Catch sdbus-cpp exception to print better error (#19)
$ hypridle
[...]
[LOG] wayland done, registering dbus
Abort
2024-02-27 13:07:22 +00:00
Mihai Fufezan
b9c94ed827
flake.lock: update nixpkgs 2024-02-24 17:13:26 +02:00
LOSEARDES77
b85722e41a
core: fix missing headers (#18) 2024-02-22 10:33:19 +00:00
Mathis H
a180de7bc7 nix: add default empty list for listeners option 2024-02-22 01:00:30 +02:00
ItsDrike
158c52c4a7
dbus: Actually register inhibit cookies (#14)
* Actually register inhibit cookies

* Add unregistering logic

* Fix code style
2024-02-20 18:25:50 +00:00
ItsDrike
da2624628b
dbus: Send response on uninhibit (#13) 2024-02-20 00:57:34 +00:00
Jakub Konior
c26683b60d
idleinhibit: added less than 0 inhibitor mitigation (#11) 2024-02-19 21:41:52 +00:00
Joshua Manchester
f4659b1bb8 nix: home-manager module fix service wantedby 2024-02-19 02:02:32 +02:00
56f334cbe8 core: roundtrip after registering idle timers
fixes #7
2024-02-18 23:45:05 +00:00
Mihai Fufezan
411addbd9f
Nix: update hyprlang 2024-02-18 19:26:31 +02:00
Austin Horstman
c030553b50 nix: home-manager module fixes 2024-02-18 19:25:41 +02:00
Austin Horstman
80abfa0dfd nix: hypridle meta.mainProgram 2024-02-18 19:25:41 +02:00
afcef912d6 idle: ignore if inhibit locked 2024-02-18 14:58:39 +00:00
f6dd1ef9d6 dbus: handle before/after sleep in PrepareForSleep 2024-02-17 23:48:54 +00:00
22 changed files with 1093 additions and 373 deletions

101
.clang-tidy Normal file
View file

@ -0,0 +1,101 @@
WarningsAsErrors: '*'
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

View file

@ -7,8 +7,35 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- 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

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
.vscode/
build/
protocols/
.clangd/
.direnv/
.cache/
compile_commands.json

View file

@ -1,11 +1,12 @@
cmake_minimum_required(VERSION 3.19)
set(VERSION 0.1.0)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VERSION)
project(hypridle
project(
hypridle
DESCRIPTION "An idle management daemon for Hyprland"
VERSION ${VERSION}
)
VERSION ${VERSION})
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
@ -17,59 +18,88 @@ else()
message(STATUS "Configuring hypridle in Release with CMake")
endif()
include_directories(
.
"protocols/"
)
add_compile_definitions(HYPRIDLE_VERSION="${VERSION}")
include_directories(. "protocols/")
include(GNUInstallDirs)
# configure
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
-Wno-missing-field-initializers -Wno-narrowing)
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)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprlang>=0.4.0 sdbus-c++)
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
find_program(WaylandScanner NAMES wayland-scanner)
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
execute_process(
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
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}")
function(protocol protoPath protoName external)
if (external)
execute_process(
COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hypridle PRIVATE protocols/${protoName}-protocol.c)
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()
execute_process(
COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hypridle PRIVATE protocols/${protoName}-protocol.c)
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 the dir won't be there
protocol("staging/ext-idle-notify/ext-idle-notify-v1.xml" "ext-idle-notify-v1" false)
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)

View file

@ -16,7 +16,9 @@ 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 {
@ -32,18 +34,41 @@ will make those events ignored.
## Dependencies
- wayland
- wayland-protocols
- hyprland-protocols
- hyprlang >= 0.4.0
- sdbus-c++
- hyprwayland-scanner
## Building & Installation
Building:
### Building:
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build
cmake --build ./build --config Release --target hypridle -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
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:
### 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_path>, --config <config_path>: specify a config path, by default
set to ${XDG_CONFIG_HOME}/hypr/hypridle.conf
-q, --quiet
-v, --verbose
```

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.7

37
assets/example.conf Normal file
View file

@ -0,0 +1,37 @@
# 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 dpms on # to avoid having to press a key twice to turn on the display.
}
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 dpms off # screen off when timeout has passed.
on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired.
}
listener {
timeout = 1800 # 30min.
on-timeout = systemctl suspend # suspend pc.
}

112
flake.lock generated
View file

@ -1,17 +1,46 @@
{
"nodes": {
"hyprlang": {
"hyprland-protocols": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1708197082,
"narHash": "sha256-6jMgmGkZLXUHVSkUQi9NLhFD3KYrG8merm0T9qiUdyA=",
"lastModified": 1749046714,
"narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
"rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-protocols",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1749145882,
"narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "65a7f870a682bd313ae978d76e61ebc72fbd64cd",
"rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676",
"type": "github"
},
"original": {
@ -20,13 +49,59 @@
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1749135356,
"narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1749145760,
"narHash": "sha256-IHaGWpGrv7seFWdw/1A+wHtTsPlOGIKMrk1TUIYJEFI=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "817918315ea016cc2d94004bfb3223b5fd9dfcc6",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1702645756,
"narHash": "sha256-qKI6OR3TYJYQB3Q8mAZ+DG4o/BR9ptcv9UnRV2hzljc=",
"owner": "nixos",
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "40c3c94c241286dd2243ea34d3aef8a488f9e4d0",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {
@ -38,8 +113,27 @@
},
"root": {
"inputs": {
"hyprland-protocols": "hyprland-protocols",
"hyprlang": "hyprlang",
"nixpkgs": "nixpkgs"
"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"
}
}
},

View file

@ -3,40 +3,63 @@
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 = inputs: let
inherit (inputs.nixpkgs) lib;
genSystems = lib.genAttrs [
# Add more systems if they are supported
"x86_64-linux"
"aarch64-linux"
];
pkgsFor = genSystems (system:
import inputs.nixpkgs {
overlays = [inputs.self.overlays.default];
inherit system;
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];
});
in {
overlays = import ./nix/overlays.nix {inherit inputs lib;};
packages = genSystems (system: {
packages = eachSystem (system: {
default = self.packages.${system}.hypridle;
inherit (pkgsFor.${system}) hypridle;
default = inputs.self.packages.${system}.hypridle;
});
homeManagerModules = {
hypridle = import ./nix/hm-module.nix inputs.self;
default = inputs.self.homeManagerModules.hypridle;
default = self.homeManagerModules.hypridle;
hypridle = builtins.throw "hypridle: the flake HM module has been removed. Use the module from Home Manager upstream.";
};
checks = genSystems (system: inputs.self.packages.${system});
checks = eachSystem (system: self.packages.${system});
formatter = genSystems (system: pkgsFor.${system}.alejandra);
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}

View file

@ -1,13 +1,17 @@
{
lib,
stdenv,
cmake,
pkg-config,
hyprland-protocols,
hyprlang,
hyprutils,
hyprwayland-scanner,
lib,
pkg-config,
sdbus-cpp,
stdenv,
systemd,
wayland,
wayland-protocols,
wayland-scanner,
version ? "git",
}:
stdenv.mkDerivation {
@ -17,11 +21,15 @@ stdenv.mkDerivation {
nativeBuildInputs = [
cmake
hyprwayland-scanner
pkg-config
wayland-scanner
];
buildInputs = [
hyprland-protocols
hyprlang
hyprutils
sdbus-cpp
systemd
wayland
@ -33,5 +41,6 @@ stdenv.mkDerivation {
description = "An idle management daemon for Hyprland";
license = lib.licenses.bsd3;
platforms = lib.platforms.linux;
mainProgram = "hypridle";
};
}

View file

@ -1,84 +0,0 @@
self: {
config,
pkgs,
lib,
...
}: let
inherit (builtins) toString;
inherit (lib.types) str int package;
inherit (lib.modules) mkIf;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.meta) getExe;
cfg = config.services.hypridle;
in {
options.services.hypridle = {
enable = mkEnableOption "Hypridle, Hyprland's idle daemon";
package = mkOption {
description = "The hypridle package";
type = package;
default = self.packages.${pkgs.stdenv.hostPlatform.system}.hypridle;
};
timeout = {
description = "The timeout for the hypridle service, in seconds";
type = int;
default = 500;
};
onTimeout = {
description = "The command to run when the timeout is reached";
type = str;
default = "echo 'timeout reached'";
};
onResume = {
description = "The command to run when the service resumes";
type = str;
default = "echo 'service resumed'";
};
lockCmd = {
description = "The command to run when the service locks";
type = str;
default = "echo 'lock!'";
};
unlockCmd = {
description = "The command to run when the service unlocks";
type = str;
default = "echo 'unlock!'";
};
beforeSleepCmd = {
description = "The command to run before the service sleeps";
type = str;
default = "echo 'Zzz...'";
};
};
config = mkIf cfg.enable {
xdg.configFile."hypr/hypridle.conf".text = ''
listener {
timeout = ${toString cfg.timeout};
on-timeout = ${cfg.onTimeout}
on-resume = ${cfg.onResume}
lock_cmd = ${cfg.lockCmd}
unlock_cmd = ${cfg.unlockCmd}
before_sleep_cmd = ${cfg.beforeSleepCmd}
}
'';
systemd.user.services.hypridle = {
description = "Hypridle";
after = ["graphical-session.target"];
wantedBy = ["default.target"];
serviceConfig = {
ExecStart = "${getExe cfg.package}";
Restart = "always";
RestartSec = "10";
};
};
};
}

View file

@ -7,15 +7,36 @@
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in {
default = lib.composeManyExtensions [
default = inputs.self.overlays.hypridle;
hypridle = 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.gcc13Stdenv;
version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
stdenv = prev.gcc15Stdenv;
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
inherit (final) hyprlang;
};
})
];
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=";
};
});
};
}

View file

@ -1,21 +1,36 @@
#include "ConfigManager.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include <hyprutils/path/Path.hpp>
#include <filesystem>
static std::string getConfigDir() {
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
if (xdgConfigHome && std::filesystem::path(xdgConfigHome).is_absolute())
return xdgConfigHome;
return getenv("HOME") + std::string("/.config");
}
#include <glob.h>
#include <cstring>
static std::string getMainConfigPath() {
return getConfigDir() + "/hypr/hypridle.conf";
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.");
}
CConfigManager::CConfigManager() : m_config(getMainConfigPath().c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = false}) {
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;
configHeadPath = 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() {
@ -23,11 +38,23 @@ void CConfigManager::init() {
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.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();
@ -60,6 +87,8 @@ Hyprlang::CParseResult CConfigManager::postParse() {
rule.onTimeout = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("listener", "on-timeout", k.c_str()));
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()));
if (timeout == -1) {
result.setError("Category has a missing timeout setting");
continue;
@ -69,7 +98,8 @@ Hyprlang::CParseResult CConfigManager::postParse() {
}
for (auto& r : m_vRules) {
Debug::log(LOG, "Registered timeout rule for {}s:\n on-timeout: {}\n on-resume: {}", r.timeout, r.onTimeout, r.onResume);
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);
}
return result;
@ -79,6 +109,55 @@ std::vector<CConfigManager::STimeoutRule> CConfigManager::getRules() {
return m_vRules;
}
void* const* CConfigManager::getValuePtr(const std::string& name) {
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
std::optional<std::string> CConfigManager::handleSource(const std::string& command, const std::string& rawpath) {
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));
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 {};
}

View file

@ -4,22 +4,31 @@
#include <hyprlang.hpp>
#include <set>
#include <vector>
#include <memory>
class CConfigManager {
public:
CConfigManager();
CConfigManager(std::string configPath);
void init();
struct STimeoutRule {
uint64_t timeout = 0;
std::string onTimeout = "";
std::string onResume = "";
bool ignoreInhibit = false;
};
std::vector<STimeoutRule> getRules();
void* const* getValuePtr(const std::string& name);
std::optional<std::string> handleSource(const std::string&, const std::string&);
std::string configCurrentPath, configHeadPath;
std::set<std::string> alreadyIncludedSourceFiles;
template <typename T>
Hyprlang::CSimpleConfigValue<T> getValue(const std::string& name) {
return Hyprlang::CSimpleConfigValue<T>(&m_config, name.c_str());
}
private:
Hyprlang::CConfig m_config;

View file

@ -1,12 +1,17 @@
#include "Hypridle.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "signal.h"
#include "csignal"
#include <sys/wait.h>
#include <sys/poll.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <algorithm>
#include <thread>
#include <mutex>
#include <hyprutils/os/Process.hpp>
CHypridle::CHypridle() {
m_sWaylandState.display = wl_display_connect(nullptr);
@ -16,36 +21,32 @@ CHypridle::CHypridle() {
}
}
void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
g_pHypridle->onGlobal(data, registry, name, interface, version);
}
void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) {
g_pHypridle->onGlobalRemoved(data, registry, name);
}
inline const wl_registry_listener registryListener = {
.global = handleGlobal,
.global_remove = handleGlobalRemove,
};
void handleIdled(void* data, ext_idle_notification_v1* ext_idle_notification_v1) {
g_pHypridle->onIdled((CHypridle::SIdleListener*)data);
}
void handleResumed(void* data, ext_idle_notification_v1* ext_idle_notification_v1) {
g_pHypridle->onResumed((CHypridle::SIdleListener*)data);
}
inline const ext_idle_notification_v1_listener idleListener = {
.idled = handleIdled,
.resumed = handleResumed,
};
void CHypridle::run() {
m_sWaylandState.registry = wl_display_get_registry(m_sWaylandState.display);
m_sWaylandState.registry = makeShared<CCWlRegistry>((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);
wl_registry_add_listener(m_sWaylandState.registry, &registryListener, nullptr);
if (IFACE == ext_idle_notifier_v1_interface.name) {
m_sWaylandIdleState.notifier =
makeShared<CCExtIdleNotifierV1>((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<CCHyprlandLockNotifierV1>((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<CCWlSeat>((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);
@ -54,6 +55,8 @@ void CHypridle::run() {
exit(1);
}
static const auto IGNOREWAYLANDINHIBIT = g_pConfigManager->getValue<Hyprlang::INT>("general:ignore_wayland_inhibit");
const auto RULES = g_pConfigManager->getRules();
m_sWaylandIdleState.listeners.resize(RULES.size());
@ -62,11 +65,29 @@ void CHypridle::run() {
for (size_t i = 0; i < RULES.size(); ++i) {
auto& l = m_sWaylandIdleState.listeners[i];
const auto& r = RULES[i];
l.notification = ext_idle_notifier_v1_get_idle_notification(m_sWaylandIdleState.notifier, r.timeout * 1000 /* ms */, m_sWaylandState.seat);
l.onRestore = r.onResume;
l.onTimeout = r.onTimeout;
l.ignoreInhibit = r.ignoreInhibit;
ext_idle_notification_v1_add_listener(l.notification, &idleListener, &l);
if (*IGNOREWAYLANDINHIBIT || r.ignoreInhibit)
l.notification =
makeShared<CCExtIdleNotificationV1>(m_sWaylandIdleState.notifier->sendGetInputIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource()));
else
l.notification =
makeShared<CCExtIdleNotificationV1>(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<CCHyprlandLockNotificationV1>(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");
@ -78,12 +99,53 @@ void CHypridle::run() {
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<Hyprlang::INT>("general:inhibit_sleep");
static const auto SLEEPCMD = g_pConfigManager->getValue<Hyprlang::STRING>("general:before_sleep_cmd");
static const auto LOCKCMD = g_pConfigManager->getValue<Hyprlang::STRING>("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();
}
void CHypridle::enterEventLoop() {
nfds_t pollfdsCount = m_sDBUSState.screenSaverServiceConnection ? 3 : 2;
pollfd pollfds[] = {
{
.fd = m_sDBUSState.connection->getEventLoopPollData().fd,
@ -99,16 +161,16 @@ void CHypridle::enterEventLoop() {
},
};
std::thread pollThr([this, &pollfds]() {
std::thread pollThr([this, &pollfds, &pollfdsCount]() {
while (1) {
int ret = poll(pollfds, m_sDBUSState.screenSaverServiceConnection ? 3 : 2, 5000 /* 5 seconds, reasonable. It's because we might need to terminate */);
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 < 3; ++i) {
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;
@ -133,7 +195,7 @@ void CHypridle::enterEventLoop() {
m_sEventLoopInternals.loopRequestMutex.unlock(); // unlock, we are ready to take events
std::unique_lock lk(m_sEventLoopInternals.loopMutex);
if (m_sEventLoopInternals.shouldProcess == false) // avoid a lock if a thread managed to request something already since we .unlock()ed
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.loopRequestMutex.lock(); // lock incoming events
@ -147,7 +209,7 @@ void CHypridle::enterEventLoop() {
if (pollfds[0].revents & POLLIN /* dbus */) {
Debug::log(TRACE, "got dbus event");
while (m_sDBUSState.connection->processPendingRequest()) {
while (m_sDBUSState.connection->processPendingEvent()) {
;
}
}
@ -163,9 +225,9 @@ void CHypridle::enterEventLoop() {
}
}
if (pollfds[2].revents & POLLIN /* dbus2 */) {
if (pollfdsCount > 2 && pollfds[2].revents & POLLIN /* dbus2 */) {
Debug::log(TRACE, "got dbus event");
while (m_sDBUSState.screenSaverServiceConnection->processPendingRequest()) {
while (m_sDBUSState.screenSaverServiceConnection->processPendingEvent()) {
;
}
}
@ -181,82 +243,25 @@ void CHypridle::enterEventLoop() {
Debug::log(ERR, "[core] Terminated");
}
void CHypridle::onGlobal(void* data, struct wl_registry* registry, 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 = (ext_idle_notifier_v1*)wl_registry_bind(registry, name, &ext_idle_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 = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, version);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
}
}
void CHypridle::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) {
;
}
static void spawn(const std::string& args) {
Debug::log(LOG, "Executing {}", args);
int socket[2];
if (pipe(socket) != 0) {
Debug::log(LOG, "Unable to create pipe for fork");
}
pid_t child, grandchild;
child = fork();
if (child < 0) {
close(socket[0]);
close(socket[1]);
Debug::log(LOG, "Fail to create the first fork");
return;
}
if (child == 0) {
// run in child
sigset_t set;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
grandchild = fork();
if (grandchild == 0) {
// run in grandchild
close(socket[0]);
close(socket[1]);
execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr);
// exit grandchild
_exit(0);
}
close(socket[0]);
write(socket[1], &grandchild, sizeof(grandchild));
close(socket[1]);
// exit child
_exit(0);
}
// run in parent
close(socket[1]);
read(socket[0], &grandchild, sizeof(grandchild));
close(socket[0]);
// clear child and leave child to init
waitpid(child, NULL, 0);
if (child < 0) {
Debug::log(LOG, "Failed to create the second fork");
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 {}", grandchild);
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.");
@ -269,6 +274,11 @@ void CHypridle::onIdled(SIdleListener* pListener) {
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);
return;
}
if (pListener->onRestore.empty()) {
Debug::log(LOG, "Ignoring, onRestore is empty.");
@ -281,12 +291,63 @@ void CHypridle::onResumed(SIdleListener* pListener) {
void CHypridle::onInhibit(bool lock) {
m_iInhibitLocks += lock ? 1 : -1;
if (m_iInhibitLocks < 0)
Debug::log(WARN, "BUG THIS: inhibit locks < 0");
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<Hyprlang::INT>("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<CCExtIdleNotificationV1>(m_sWaylandIdleState.notifier->sendGetInputIdleNotification(r.timeout * 1000 /* ms */, m_sWaylandState.seat->resource()));
else
l.notification =
makeShared<CCExtIdleNotificationV1>(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<Hyprlang::STRING>("general:on_lock_cmd");
if (!std::string{*LOCKCMD}.empty())
spawn(*LOCKCMD);
if (m_inhibitSleepBehavior == SLEEP_INHIBIT_LOCK_NOTIFY)
uninhibitSleep();
}
void CHypridle::onUnlocked() {
Debug::log(LOG, "Wayland session got unlocked");
m_isLocked = false;
if (m_inhibitSleepBehavior == SLEEP_INHIBIT_LOCK_NOTIFY)
inhibitSleep();
static const auto UNLOCKCMD = g_pConfigManager->getValue<Hyprlang::STRING>("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)
@ -300,110 +361,283 @@ void CHypridle::registerDbusInhibitCookie(CHypridle::SDbusInhibitCookie& cookie)
m_sDBUSState.inhibitCookies.push_back(cookie);
}
void handleDbusLogin(sdbus::Message& msg) {
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 auto* const PLOCKCMD = (Hyprlang::STRING const*)g_pConfigManager->getValuePtr("general:lock_cmd");
static auto* const PUNLOCKCMD = (Hyprlang::STRING const*)g_pConfigManager->getValuePtr("general:unlock_cmd");
static const auto LOCKCMD = g_pConfigManager->getValue<Hyprlang::STRING>("general:lock_cmd");
static const auto UNLOCKCMD = g_pConfigManager->getValue<Hyprlang::STRING>("general:unlock_cmd");
Debug::log(LOG, "Got dbus .Session");
const auto MEMBER = msg.getMemberName();
const std::string MEMBER = msg.getMemberName();
if (MEMBER == "Lock") {
Debug::log(LOG, "Got Lock from dbus");
if (!std::string{*PLOCKCMD}.empty()) {
Debug::log(LOG, "Locking with {}", *PLOCKCMD);
spawn(*PLOCKCMD);
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{*PUNLOCKCMD}.empty()) {
Debug::log(LOG, "Locking with {}", *PUNLOCKCMD);
spawn(*PUNLOCKCMD);
if (!std::string{*UNLOCKCMD}.empty()) {
Debug::log(LOG, "Unlocking with {}", *UNLOCKCMD);
spawn(*UNLOCKCMD);
}
}
}
void handleDbusSleep(sdbus::Message& msg) {
const auto MEMBER = msg.getMemberName();
static void handleDbusSleep(sdbus::Message msg) {
const std::string MEMBER = msg.getMemberName();
if (MEMBER != "PrepareForSleep")
return;
static auto* const PSLEEPCMD = (Hyprlang::STRING const*)g_pConfigManager->getValuePtr("general:before_sleep_cmd");
bool toSleep = true;
msg >> toSleep;
Debug::log(LOG, "Got PrepareForSleep from dbus");
static const auto SLEEPCMD = g_pConfigManager->getValue<Hyprlang::STRING>("general:before_sleep_cmd");
static const auto AFTERSLEEPCMD = g_pConfigManager->getValue<Hyprlang::STRING>("general:after_sleep_cmd");
if (std::string{*PSLEEPCMD}.empty())
return;
Debug::log(LOG, "Got PrepareForSleep from dbus with sleep {}", toSleep);
Debug::log(LOG, "Running before-sleep: {}", *PSLEEPCMD);
spawn(*PSLEEPCMD);
std::string cmd = toSleep ? *SLEEPCMD : *AFTERSLEEPCMD;
if (!toSleep)
g_pHypridle->handleInhibitOnDbusSleep(toSleep);
if (!cmd.empty())
spawn(cmd);
if (toSleep)
g_pHypridle->handleInhibitOnDbusSleep(toSleep);
}
void handleDbusScreensaver(sdbus::MethodCall call, bool inhibit) {
std::string app = "?", reason = "?";
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);
}
}
if (inhibit) {
call >> app;
call >> reason;
} else {
uint32_t cookie = 0;
call >> cookie;
static void handleDbusBlockInhibitsPropertyChanged(sdbus::Message msg) {
std::string interface;
std::map<std::string, sdbus::Variant> changedProperties;
msg >> interface >> changedProperties;
if (changedProperties.contains("BlockInhibited")) {
handleDbusBlockInhibits(changedProperties["BlockInhibited"].get<std::string>());
}
}
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 {} with content {}", inhibit, app, reason);
Debug::log(LOG, "ScreenSaver inhibit: {} dbus message from {} (owner: {}) with content {}", inhibit, app, ownerID, reason);
static auto* const PIGNORE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:ignore_dbus_inhibit");
if (!**PIGNORE) {
if (inhibit)
g_pHypridle->onInhibit(true);
else
else if (cookieFound)
g_pHypridle->onInhibit(false);
}
static int cookieID = 1337;
static uint32_t cookieID = 1337;
if (inhibit) {
auto reply = call.createReply();
reply << uint32_t{cookieID++};
reply.send();
auto cookie = CHypridle::SDbusInhibitCookie{.cookie = cookieID, .app = app, .reason = reason, .ownerID = ownerID};
Debug::log(LOG, "Cookie {} sent", cookieID - 1);
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() {
auto proxy = sdbus::createProxy("org.freedesktop.login1", "/org/freedesktop/login1");
auto method = proxy->createMethodCall("org.freedesktop.login1.Manager", "GetSession");
method << "auto";
auto reply = proxy->callMethod(method);
static const auto IGNOREDBUSINHIBIT = g_pConfigManager->getValue<Hyprlang::INT>("general:ignore_dbus_inhibit");
static const auto IGNORESYSTEMDINHIBIT = g_pConfigManager->getValue<Hyprlang::INT>("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;
reply >> 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());
m_sDBUSState.connection->addMatch("type='signal',path='" + path + "',interface='org.freedesktop.login1.Session'", handleDbusLogin, sdbus::floating_slot_t{});
m_sDBUSState.connection->addMatch("type='signal',path='/org/freedesktop/login1',interface='org.freedesktop.login1.Manager'", handleDbusSleep, sdbus::floating_slot_t{});
if (!*IGNORESYSTEMDINHIBIT) {
m_sDBUSState.connection->addMatch("type='signal',path='/org/freedesktop/login1',interface='org.freedesktop.DBus.Properties'", ::handleDbusBlockInhibitsPropertyChanged);
// attempt to register as ScreenSaver
try {
m_sDBUSState.screenSaverServiceConnection = sdbus::createSessionBusConnection("org.freedesktop.ScreenSaver");
m_sDBUSState.screenSaverObject = sdbus::createObject(*m_sDBUSState.screenSaverServiceConnection, "/org/freedesktop/ScreenSaver");
std::string value = (proxy->getProperty("BlockInhibited").onInterface("org.freedesktop.login1.Manager")).get<std::string>();
handleDbusBlockInhibits(value);
} catch (std::exception& e) { Debug::log(WARN, "Couldn't retrieve current systemd inhibits ({})", e.what()); }
}
m_sDBUSState.screenSaverObject->registerMethod("org.freedesktop.ScreenSaver", "Inhibit", "ss", "u", [&](sdbus::MethodCall c) { handleDbusScreensaver(c, true); });
m_sDBUSState.screenSaverObject->registerMethod("org.freedesktop.ScreenSaver", "UnInhibit", "u", "", [&](sdbus::MethodCall c) { handleDbusScreensaver(c, false); });
if (!*IGNOREDBUSINHIBIT) {
// attempt to register as ScreenSaver
std::string paths[] = {
"/org/freedesktop/ScreenSaver",
"/ScreenSaver",
};
m_sDBUSState.screenSaverObject->finishRegistration();
} catch (std::exception& e) { Debug::log(ERR, "Failed registering for /org/freedesktop/ScreenSaver, perhaps taken?\nerr: {}", e.what()); }
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());
}))
.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();
}

View file

@ -2,24 +2,30 @@
#include <memory>
#include <vector>
#include <wayland-client.h>
#include <sdbus-c++/sdbus-c++.h>
#include <hyprutils/os/FileDescriptor.hpp>
#include <condition_variable>
#include "ext-idle-notify-v1-protocol.h"
#include "wayland.hpp"
#include "ext-idle-notify-v1.hpp"
#include "hyprland-lock-notify-v1.hpp"
#include "../defines.hpp"
class CHypridle {
public:
CHypridle();
struct SIdleListener {
ext_idle_notification_v1* notification = nullptr;
SP<CCExtIdleNotificationV1> notification = nullptr;
std::string onTimeout = "";
std::string onRestore = "";
bool ignoreInhibit = false;
};
struct SDbusInhibitCookie {
uint32_t cookie = 0;
std::string app, reason;
std::string app, reason, ownerID;
};
void run();
@ -32,24 +38,43 @@ class CHypridle {
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;
wl_registry* registry = nullptr;
wl_seat* seat = nullptr;
SP<CCWlRegistry> registry = nullptr;
SP<CCWlSeat> seat = nullptr;
SP<CCHyprlandLockNotifierV1> lockNotifier = nullptr;
SP<CCHyprlandLockNotificationV1> lockNotification = nullptr;
} m_sWaylandState;
struct {
ext_idle_notifier_v1* notifier = nullptr;
SP<CCExtIdleNotifierV1> notifier = nullptr;
std::vector<SIdleListener> listeners;
} m_sWaylandIdleState;
@ -57,8 +82,10 @@ class CHypridle {
struct {
std::unique_ptr<sdbus::IConnection> connection;
std::unique_ptr<sdbus::IConnection> screenSaverServiceConnection;
std::unique_ptr<sdbus::IObject> screenSaverObject;
std::unique_ptr<sdbus::IProxy> login;
std::vector<std::unique_ptr<sdbus::IObject>> screenSaverObjects;
std::vector<SDbusInhibitCookie> inhibitCookies;
Hyprutils::OS::CFileDescriptor sleepInhibitFd;
} m_sDBUSState;
struct {

8
src/defines.hpp Normal file
View file

@ -0,0 +1,8 @@
#include "wayland.hpp"
#include "ext-idle-notify-v1.hpp"
#include "hyprland-lock-notify-v1.hpp"
#include <hyprutils/memory/WeakPtr.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer

View file

@ -53,6 +53,6 @@ namespace Debug {
std::cout << "] ";
}
std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n";
std::cout << std::vformat(fmt, std::make_format_args(args...)) << std::endl;
}
};

View file

@ -0,0 +1,19 @@
#include <filesystem>
#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);
}

View file

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string absolutePath(const std::string&, const std::string&);

View file

@ -1,8 +1,10 @@
#include "config/ConfigManager.hpp"
#include "core/Hypridle.hpp"
#include "helpers/Log.hpp"
int main(int argc, char** argv, char** envp) {
std::string configPath;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
@ -12,10 +14,45 @@ int main(int argc, char** argv, char** envp) {
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 <path> Specify a custom config file path\n"
" -h, --help Show this help message");
return 0;
}
}
try {
g_pConfigManager = std::make_unique<CConfigManager>();
g_pConfigManager = std::make_unique<CConfigManager>(configPath);
g_pConfigManager->init();
} catch (const char* err) {
Debug::log(CRIT, "ConfigManager threw: {}", err);

View file

@ -0,0 +1,14 @@
[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