119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
// 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 <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
//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))
|
|
}
|