use cpal::{traits::{DeviceTrait, HostTrait, StreamTrait}, SampleFormat}; use futures_util::stream::StreamExt; use std::collections::HashSet; use swayipc_async::{Connection, Event, EventType, WindowChange}; #[tokio::main(flavor = "current_thread")] async fn main() { let sys_dbus = Box::leak(Box::new(zbus::Connection::system().await.unwrap())); let _ses_dbus = Box::leak(Box::new(zbus::Connection::session().await.unwrap())); let mut handlers = Vec::>::new(); let mut panic = true; for args in std::env::args().skip(1) { handlers.push(match args.as_str() { "system76-scheduler" => Box::new(System76::new(sys_dbus).await), "empty-sound" => { panic = false; tokio::spawn(async { play_empty_sound().await; }); continue; } _ => panic!("handler not supported"), }) } if handlers.is_empty() { if panic { panic!("no handlers set up"); } else { futures_util::future::pending::<()>().await; } } let mut subs = HashSet::new(); for handler in &mut handlers { handler.register(&mut subs); } let subs = subs.into_iter().collect::>(); loop { drop(start(&subs, &mut handlers).await); tokio::time::sleep(std::time::Duration::from_secs(10)).await; } } async fn start( subs: &[EventType], handlers: &mut [Box], ) -> Result<(), swayipc_async::Error> { let mut events = Connection::new().await?.subscribe(&subs).await?; while let Some(event) = events.next().await { match event { Ok(event) => { for handler in &mut *handlers { handler.handle(&event); } } Err(err) => match err { swayipc_async::Error::Io(_) | swayipc_async::Error::InvalidMagic(_) | swayipc_async::Error::SubscriptionFailed(_) => return Err(err), _ => {} }, } } Ok(()) } async fn play_empty_sound() { let device = cpal::default_host() .default_output_device() .expect("no output device available"); let supported_config = device .supported_output_configs() .unwrap() .find(|config| { config.sample_format() == SampleFormat::F32 && (config.min_sample_rate()..=config.max_sample_rate()) .contains(&cpal::SampleRate(44100)) && config.channels() == 1 }) .unwrap() .with_sample_rate(cpal::SampleRate(44100)); let config = supported_config.into(); let stream = Box::leak(Box::new( device .build_output_stream( &config, |data: &mut [f32], _: &cpal::OutputCallbackInfo| { for sample in data.iter_mut() { *sample = 1.0 / 1985.0; } }, |err| eprintln!("an error occurred on the output audio stream: {}", err), None, ) .unwrap(), )); stream.play().unwrap(); futures_util::future::pending::<()>().await; } trait SwayIpcHandler { fn register(&mut self, subs: &mut HashSet); fn handle(&mut self, event: &Event); } #[zbus::dbus_proxy( interface = "com.system76.Scheduler", default_service = "com.system76.Scheduler", default_path = "/com/system76/Scheduler" )] pub trait System76Scheduler { /// This process will have its process group prioritized over background processes fn set_foreground_process(&mut self, pid: u32) -> zbus::fdo::Result<()>; } struct System76<'a> { proxy: System76SchedulerProxy<'a>, } impl<'a> System76<'a> { pub async fn new(dbus: &'a zbus::Connection) -> System76<'a> { let proxy = System76SchedulerProxy::new(dbus).await.unwrap(); Self { proxy } } } impl SwayIpcHandler for System76<'static> { fn register(&mut self, subs: &mut HashSet) { subs.insert(EventType::Window); } fn handle(&mut self, event: &Event) { if let Event::Window(window) = event { if window.change != WindowChange::Focus { return; } if let Some(pid) = window.container.pid.and_then(|x| u32::try_from(x).ok()) { let mut proxy = self.proxy.clone(); tokio::spawn(async move { drop(proxy.set_foreground_process(pid).await); }); } } } }