// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . //go:build linux && cgo package console import ( "errors" "sync" "syscall" ) // readinessWatcher reports readiness without reading the observed descriptors. // The application loop owns device reads, acknowledgement and descriptor lifetime. type readinessWatcher struct { fd int mask uint32 wake [2]int events chan error stop chan struct{} workers sync.WaitGroup } func newReadinessWatcher(mask uint32) (*readinessWatcher, error) { fd, err := syscall.EpollCreate1(syscall.EPOLL_CLOEXEC) if err != nil { return nil, err } w := &readinessWatcher{fd: fd, mask: mask, events: make(chan error, 1), stop: make(chan struct{})} if err := syscall.Pipe2(w.wake[:], syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil { syscall.Close(fd) return nil, err } event := syscall.EpollEvent{Fd: int32(w.wake[0]), Events: syscall.EPOLLIN} if err := syscall.EpollCtl(fd, syscall.EPOLL_CTL_ADD, w.wake[0], &event); err != nil { syscall.Close(w.wake[0]) syscall.Close(w.wake[1]) syscall.Close(fd) return nil, err } w.workers.Add(1) go w.watch() return w, nil } func (w *readinessWatcher) add(fd int) error { return w.control(syscall.EPOLL_CTL_ADD, fd) } func (w *readinessWatcher) arm(fd int) error { return w.control(syscall.EPOLL_CTL_MOD, fd) } func (w *readinessWatcher) control(operation, fd int) error { event := syscall.EpollEvent{Fd: int32(fd), Events: w.mask | syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLONESHOT} return syscall.EpollCtl(w.fd, operation, fd, &event) } func (w *readinessWatcher) notify() { select { case w.events <- nil: default: } } func (w *readinessWatcher) watch() { defer w.workers.Done() var events [16]syscall.EpollEvent for { select { case <-w.stop: return default: } // Device readiness and shutdown wake epoll directly; there is no polling timer. count, err := syscall.EpollWait(w.fd, events[:], -1) if errors.Is(err, syscall.EINTR) { continue } if err != nil { select { case w.events <- err: case <-w.stop: } return } if count > 0 { for _, event := range events[:count] { if int(event.Fd) == w.wake[0] { return } } w.notify() } } } func (w *readinessWatcher) close() error { close(w.stop) _, err := syscall.Write(w.wake[1], []byte{1}) w.workers.Wait() return errors.Join(err, syscall.Close(w.wake[0]), syscall.Close(w.wake[1]), syscall.Close(w.fd)) }