1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//! ```no_run
//! use futures_util::StreamExt;
//!
//! #[tokio::main(flavor = "current_thread")]
//! async fn main() -> std::io::Result<()> {
//! let mut ac_plug_events = acpid_plug::connect().await?;
//!
//! while let Some(event) = ac_plug_events.next().await {
//! match event {
//! Ok(event) => println!("{:?}", event),
//! Err(why) => eprintln!("error: {}", why),
//! }
//! }
//!
//! Ok(())
//! }
//! ```
use std::path::Path;
use std::task::Poll;
use futures_util::FutureExt;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::UnixStream;
const DEFAULT_SOCKET: &str = "/var/run/acpid.socket";
const BAT0_STATUS: &str = "/sys/class/power_supply/BAT0/status";
const BAT1_STATUS: &str = "/sys/class/power_supply/BAT1/status";
/// Listens for AC plug events from `/var/run/acpid.socket`.
pub async fn connect() -> std::io::Result<AcPlugEvents> {
AcPlugEvents::connect().await
}
/// Listens for AC plug events from a custom socket.
pub async fn with_socket<P: AsRef<Path>>(socket: P) -> std::io::Result<AcPlugEvents> {
AcPlugEvents::with_socket(socket).await
}
/// Whether the power adapter has been plugged or unplugged.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum Event {
Plugged,
Unplugged,
}
/// A stream of power adapter plug events.
pub struct AcPlugEvents {
reader: BufReader<UnixStream>,
line: String,
plugged: bool,
}
impl AcPlugEvents {
/// Listens for AC plug events from `/var/run/acpid.socket`.
pub async fn connect() -> std::io::Result<Self> {
Self::with_socket(DEFAULT_SOCKET).await
}
/// Listens for AC plug events from a custom socket.
pub async fn with_socket<P: AsRef<Path>>(socket: P) -> std::io::Result<Self> {
let stream = UnixStream::connect(socket).await?;
Ok(Self {
reader: BufReader::new(stream),
line: String::new(),
plugged: {
let status = match tokio::fs::read_to_string(BAT1_STATUS).await {
Ok(string) => string,
Err(_) => tokio::fs::read_to_string(BAT0_STATUS).await?,
};
status.trim() != "Discharging"
},
})
}
}
impl futures_util::Stream for AcPlugEvents {
type Item = std::io::Result<Event>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
let mut line_read = Box::pin(this.reader.read_line(&mut this.line));
match line_read.poll_unpin(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Ok(read)) => {
if read == 0 {
return Poll::Ready(None);
}
let read_line = &this.line[..read].trim();
if read_line.starts_with("ac_adapter") {
if this.plugged {
if read_line.ends_with('0') {
this.plugged = false;
this.line.clear();
return Poll::Ready(Some(Ok(Event::Unplugged)));
}
} else if read_line.ends_with('1') {
this.plugged = true;
this.line.clear();
return Poll::Ready(Some(Ok(Event::Plugged)));
}
}
this.line.clear();
}
Poll::Ready(Err(why)) => return Poll::Ready(Some(Err(why))),
}
}
}
}