use async_std::task; use futures::{future::FutureExt, pin_mut, select, AsyncReadExt, StreamExt}; use log::{debug, error, info, warn}; use logind_zbus::manager::{InhibitType, ManagerProxy}; use std::{os::unix::io::AsRawFd, process::exit, time::Duration}; use zbus::Connection; use bscreensaver_command::{bscreensaver_command, BCommand}; use bscreensaver_util::init_logging; #[async_std::main] async fn main() { init_logging("BSCREENSAVER_SYSTEMD_LOG"); let xcb_handle = task::spawn(xcb_task()).fuse(); let dbus_handle = task::spawn(dbus_task()).fuse(); pin_mut!(xcb_handle, dbus_handle); let res = loop { select! { _ = xcb_handle => { info!("Lost connection to X server; quitting"); break Ok(()); }, res = dbus_handle => { match res { Err(err) => error!("Lost connection to the session bus: {}", err), Ok(_) => error!("DBus task exited normally; this should not happen!"), } break Err(()); }, }; }; if let Err(_) = res { exit(1); } } async fn xcb_task() -> anyhow::Result<()> { let (xcb_conn, _) = task::block_on(async { xcb::Connection::connect(None) })?; let mut xcb_conn = async_xcb::AsyncConnection::new(xcb_conn)?; // We need to drain the XCB connection periodically. Even though we have not // asked for any events, we'll still get stuff like MappingNotify if the keyboard // settings change. loop { let mut buf = [0u8; 512]; xcb_conn.read(&mut buf).await?; } } async fn dbus_task() -> anyhow::Result<()> { let system_bus = Connection::system().await?; let manager_proxy = ManagerProxy::new(&system_bus).await?; let mut prepare_for_sleep_stream = manager_proxy.receive_prepare_for_sleep().await?; let mut inhibit_fd = Some(register_sleep_lock(&manager_proxy).await?); loop { if let Some(prepare_for_sleep) = prepare_for_sleep_stream.next().await { if *prepare_for_sleep.args()?.start() { debug!("Preparing for sleep"); if let Err(err) = do_bscreensaver_command(BCommand::Lock).await { warn!("Failed to lock screen: {}", err); } if let Some(fd) = inhibit_fd.take() { if let Err(err) = nix::unistd::close(fd.as_raw_fd()) { warn!("Failed to close sleep inhibit lock: {}", err); } } else { warn!("No sleep lock present"); } } else { debug!("Resuming from sleep"); if let Err(err) = do_bscreensaver_command(BCommand::Deactivate).await { warn!("Failed to deactivate screen lock: {}", err); } inhibit_fd = Some(register_sleep_lock(&manager_proxy).await?); } } } } async fn do_bscreensaver_command(command: BCommand) -> anyhow::Result<()> { task::block_on(async { bscreensaver_command(command, Some(Duration::from_secs(4))) })?; Ok(()) } async fn register_sleep_lock<'a>(manager_proxy: &ManagerProxy<'a>) -> anyhow::Result { debug!("Registering sleep lock"); // ManagerProxy uses RawFd for the return value, which rust's type system thinks is an i32, // which means the generated proxy uses the wrong dbus type signature. So instead, use a raw // Proxy instance and do it all ourselves. Ok((*manager_proxy).call("Inhibit", &(InhibitType::Sleep, "bscreensaver", "blank before sleep", "delay")).await?) }