1use std::path::Path;
23use std::task::Poll;
24
25use futures_util::FutureExt;
26use tokio::io::{AsyncBufReadExt, BufReader};
27use tokio::net::UnixStream;
28
29const DEFAULT_SOCKET: &str = "/var/run/acpid.socket";
30const BAT0_STATUS: &str = "/sys/class/power_supply/BAT0/status";
31const BAT1_STATUS: &str = "/sys/class/power_supply/BAT1/status";
32
33pub async fn connect() -> std::io::Result<AcPlugEvents> {
35 AcPlugEvents::connect().await
36}
37
38pub async fn with_socket<P: AsRef<Path>>(socket: P) -> std::io::Result<AcPlugEvents> {
40 AcPlugEvents::with_socket(socket).await
41}
42
43#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
45pub enum Event {
46 Plugged,
47 Unplugged,
48}
49
50pub struct AcPlugEvents {
52 reader: BufReader<UnixStream>,
53 line: String,
54 plugged: bool,
55}
56
57impl AcPlugEvents {
58 pub async fn connect() -> std::io::Result<Self> {
60 Self::with_socket(DEFAULT_SOCKET).await
61 }
62
63 pub async fn with_socket<P: AsRef<Path>>(socket: P) -> std::io::Result<Self> {
65 let stream = UnixStream::connect(socket).await?;
66
67 Ok(Self {
68 reader: BufReader::new(stream),
69 line: String::new(),
70 plugged: {
71 let status = match tokio::fs::read_to_string(BAT1_STATUS).await {
72 Ok(string) => string,
73 Err(_) => tokio::fs::read_to_string(BAT0_STATUS).await?,
74 };
75
76 status.trim() != "Discharging"
77 },
78 })
79 }
80
81 pub fn plugged(&self) -> bool {
83 self.plugged
84 }
85}
86
87impl futures_util::Stream for AcPlugEvents {
88 type Item = std::io::Result<Event>;
89
90 fn poll_next(
91 self: std::pin::Pin<&mut Self>,
92 cx: &mut std::task::Context<'_>,
93 ) -> Poll<Option<Self::Item>> {
94 let this = self.get_mut();
95
96 loop {
97 let mut line_read = Box::pin(this.reader.read_line(&mut this.line));
98
99 match line_read.poll_unpin(cx) {
100 Poll::Pending => return Poll::Pending,
101 Poll::Ready(Ok(read)) => {
102 if read == 0 {
103 return Poll::Ready(None);
104 }
105
106 let read_line = &this.line[..read].trim();
107
108 if read_line.starts_with("ac_adapter") {
109 if this.plugged {
110 if read_line.ends_with('0') {
111 this.plugged = false;
112 this.line.clear();
113 return Poll::Ready(Some(Ok(Event::Unplugged)));
114 }
115 } else if read_line.ends_with('1') {
116 this.plugged = true;
117 this.line.clear();
118 return Poll::Ready(Some(Ok(Event::Plugged)));
119 }
120 }
121
122 this.line.clear();
123 }
124 Poll::Ready(Err(why)) => return Poll::Ready(Some(Err(why))),
125 }
126 }
127 }
128}