Summary
Clightd's idle detection module (src/modules/idle.c) uses inotify_add_watch() with IN_ACCESS on the /dev/input/ directory to detect user input activity. However, read() calls on character device files within /dev/input/ do not generate IN_ACCESS inotify events propagated to the parent directory watch. This causes the idle detection to never see user activity, resulting in the idle timeout always firing regardless of user input.
Environment
- Kernel: Linux 6.12.78 (NixOS 25.11)
- Filesystem: devtmpfs on /dev
- Clightd version: 5.9
- Clight version: 4.11
Observed behavior
With Clight's DPMS timeout set to 600 seconds (battery), the screen always goes blank after 600 seconds even during active keyboard/mouse use. Clight's gamma and dimmer modules are disabled, isolating the issue to DPMS/idle detection.
Reproduction
The following Python script demonstrates the problem. Run it while actively moving the mouse:
import ctypes, ctypes.util, struct, os, select, time, threading
IN_ALL_EVENTS = 0x00000FFF
event_names = {
0x1: 'ACCESS', 0x2: 'MODIFY', 0x4: 'ATTRIB', 0x8: 'CLOSE_WRITE',
0x10: 'CLOSE_NOWRITE', 0x20: 'OPEN',
}
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
fd = libc.inotify_init()
wd = libc.inotify_add_watch(fd, b'/dev/input/', IN_ALL_EVENTS)
inotify_events = []
def watch_inotify(duration):
start = time.time()
while time.time() - start < duration:
r, _, _ = select.select([fd], [], [], 0.1)
if r:
data = os.read(fd, 4096)
offset = 0
while offset < len(data):
wd_ev, mask, cookie, name_len = struct.unpack_from('iIII', data, offset)
name = data[offset+16:offset+16+name_len].rstrip(b'\x00').decode()
evs = [n for bit, n in event_names.items() if mask & bit]
inotify_events.append(f'{"&".join(evs)} on {name}')
offset += 16 + name_len
watcher = threading.Thread(target=watch_inotify, args=(6,))
watcher.start()
mfd = os.open('/dev/input/mice', os.O_RDONLY)
print('Move your mouse for 5 seconds...')
reads_ok = 0
start = time.time()
while time.time() - start < 5:
r, _, _ = select.select([mfd], [], [], 1.0)
if r:
os.read(mfd, 256)
reads_ok += 1
if reads_ok > 20:
break
os.close(mfd)
watcher.join()
print(f'Successful reads: {reads_ok}')
print(f'Inotify events: {len(inotify_events)}')
for e in inotify_events:
print(f' {e}')
Result
Move your mouse for 5 seconds...
Successful reads: 21
Inotify events: 2
OPEN on mice
CLOSE_NOWRITE on mice
21 successful read() calls on /dev/input/mice, but zero IN_ACCESS events. Only OPEN and CLOSE_NOWRITE (from open()/close()) are propagated to the directory watch.
Analysis
In src/modules/idle.c line 257:
inot_wd = inotify_add_watch(inot_fd, "/dev/input/", IN_ACCESS);
This relies on IN_ACCESS events being propagated from child device files to the parent directory watcher via __fsnotify_parent(). While this propagation works for OPEN/CLOSE events, it does not work for ACCESS events on character device reads under devtmpfs (at least on kernel 6.12). A similar issue has been reported in fsnotify/fsnotify#182.
As a result, last_input (line 40, 97) is never updated, and every idle client always times out regardless of user activity.
Summary
Clightd's idle detection module (
src/modules/idle.c) usesinotify_add_watch()withIN_ACCESSon the/dev/input/directory to detect user input activity. However,read()calls on character device files within/dev/input/do not generateIN_ACCESSinotify events propagated to the parent directory watch. This causes the idle detection to never see user activity, resulting in the idle timeout always firing regardless of user input.Environment
Observed behavior
With Clight's DPMS timeout set to 600 seconds (battery), the screen always goes blank after 600 seconds even during active keyboard/mouse use. Clight's gamma and dimmer modules are disabled, isolating the issue to DPMS/idle detection.
Reproduction
The following Python script demonstrates the problem. Run it while actively moving the mouse:
Result
21 successful
read()calls on/dev/input/mice, but zeroIN_ACCESSevents. OnlyOPENandCLOSE_NOWRITE(fromopen()/close()) are propagated to the directory watch.Analysis
In
src/modules/idle.cline 257:This relies on
IN_ACCESSevents being propagated from child device files to the parent directory watcher via__fsnotify_parent(). While this propagation works forOPEN/CLOSEevents, it does not work forACCESSevents on character device reads under devtmpfs (at least on kernel 6.12). A similar issue has been reported in fsnotify/fsnotify#182.As a result,
last_input(line 40, 97) is never updated, and every idle client always times out regardless of user activity.