package main import ( "bytes" "context" "fmt" "syscall" ) // The kernel announces every change of a power supply — an adapter plugged or pulled, a battery // starting or stopping to discharge — as a uevent on a netlink socket that any account may listen // on. That is the event the switcher reacts to: no daemon, no bus client, no polling. // // upower re-announces the same changes on the system bus, and listening there would need a D-Bus // client in the bundle; udev's re-broadcast (netlink group 2) carries a libudev header. The kernel's // own group (1) is the source both of them read. // powerSupplyEvent says whether a uevent is about a power supply. func powerSupplyEvent(msg []byte) bool { for _, field := range bytes.Split(msg, []byte{0}) { if bytes.Equal(field, []byte("SUBSYSTEM=power_supply")) { return true } } return false } // listenPowerSupply opens the kernel's uevent socket and sends on the channel for each power-supply // event, never blocking: a burst of events is one wake-up. It stops when ctx ends. func listenPowerSupply(ctx context.Context) (<-chan struct{}, error) { fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, syscall.NETLINK_KOBJECT_UEVENT) if err != nil { return nil, fmt.Errorf("opening the kernel's uevent socket: %w", err) } if err := syscall.Bind(fd, &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK, Groups: 1}); err != nil { syscall.Close(fd) return nil, fmt.Errorf("joining the kernel's uevent group: %w", err) } events := make(chan struct{}, 1) go func() { <-ctx.Done() syscall.Close(fd) }() go func() { defer close(events) buf := make([]byte, 64*1024) for { n, _, err := syscall.Recvfrom(fd, buf, 0) if err != nil { if err == syscall.EINTR || err == syscall.ENOBUFS { // ENOBUFS: events were dropped. Treat it as one, since a dropped one may have // been the adapter. if err == syscall.ENOBUFS { select { case events <- struct{}{}: default: } } continue } return } if powerSupplyEvent(buf[:n]) { select { case events <- struct{}{}: default: } } } }() return events, nil }