#[macro_use] extern crate log; use dbus::channel::MatchingReceiver; use dbus::message::MatchRule; use dbus_crossroads::Crossroads; use dbus_tokio::connection; use futures::future; use serde::Deserialize; use std::{collections::HashMap, io}; use tokio_i3ipc::I3; #[derive(Debug, Clone)] struct Nanpa { config: Config, } async fn get_active_output_name() -> io::Result { let mut sock = I3::connect().await?; for ws in sock.get_workspaces().await? { debug!("ws: {:?}", ws); if ws.focused { return Ok(ws.output); } } Err(io::Error::new( io::ErrorKind::Other, "can't get active output somehow", )) } #[derive(Deserialize, Debug, Clone)] struct Config { outputs: HashMap, } fn make_workspace_name(num: u32) -> String { match num % 10 { 1 => format!("{}:", num), 2 => format!("{}:", num), 3 => format!("{}:", num), 4 => format!("{}:", num), 5 => format!("{}:", num), 6 => format!("{}:", num), 7 => format!("{}:", num), 8 => format!("{}:", num), 9 => format!("{}:", num), 0 => format!("{}:", num), _ => panic!("this should be impossible"), } } #[test] fn test_workspace_name() { for i in 1..99 { println!("{}: {}", i, make_workspace_name(i)); } } #[tokio::main] pub async fn main() -> Result<(), Box> { env_logger::init(); debug!("connecting to dbus"); let (resource, c) = connection::new_session_sync()?; tokio::spawn(async { let err = resource.await; panic!("Lost connection to D-Bus: {}", err); }); c.request_name("website.christine.nanpa", false, true, false) .await?; let mut cr = Crossroads::new(); cr.set_async_support(Some(( c.clone(), Box::new(|x| { tokio::spawn(x); }), ))); let iface_token = cr.register("website.christine.nanpa", |b| { b.method_with_cr_async( "Move", ("number",), ("reply",), |mut ctx, cr, (number,): (u32,)| { let nanpa: &mut Nanpa = cr.data_mut(ctx.path()).unwrap(); let cfg = nanpa.config.clone(); async move { let output = get_active_output_name().await.unwrap(); let number = cfg.outputs[&output] + number; let ws_name = make_workspace_name(number); debug!( "output: {}, number: {}, ws_name: {:?}", output, number, ws_name ); let mut sock = I3::connect().await.unwrap(); sock.run_command(format!("move container to workspace {}", ws_name)) .await .unwrap(); ctx.reply(Ok(("OK".to_string(),))) } }, ); b.method_with_cr_async( "Switch", ("number",), ("reply",), |mut ctx, cr, (number,): (u32,)| { let nanpa: &mut Nanpa = cr.data_mut(ctx.path()).unwrap(); let cfg = nanpa.config.clone(); async move { let output = get_active_output_name().await.unwrap(); let number = cfg.outputs[&output] + number; let ws_name = make_workspace_name(number); debug!( "output: {}, number: {}, ws_name: {:?}", output, number, ws_name ); let mut sock = I3::connect().await.unwrap(); sock.run_command(format!("workspace {}", ws_name)) .await .unwrap(); ctx.reply(Ok(("OK".to_string(),))) } }, ); }); let mut display_bases: HashMap = HashMap::new(); let mut sock = I3::connect().await?; for (i, output) in sock.get_outputs().await?.into_iter().enumerate() { let offset: u32 = i as u32 * 10; sock.run_command(format!("focus output {}", output.name)) .await?; display_bases.insert(output.name, offset); let ws_name = make_workspace_name(offset + 1); sock.run_command(format!("workspace {}", ws_name)).await?; } debug!("{:?}", display_bases); let nanpa = Nanpa { config: Config { outputs: display_bases, }, }; cr.insert("/", &[iface_token], nanpa.clone()); c.start_receive( MatchRule::new_method_call(), Box::new(move |msg, conn| { cr.handle_message(msg, conn).unwrap(); true }), ); future::pending::<()>().await; unreachable!() }