use clap::{arg, App}; use rdev::{ Event, EventType::{ KeyPress, KeyRelease, }, Key, }; use nix::unistd::{Group, Uid}; use once_cell::sync::OnceCell; use std::{ thread, sync::{ Mutex, mpsc::{ sync_channel, SyncSender, Receiver }, }, collections::HashMap, env, fs, io::prelude::*, os::unix::net::UnixStream, os::unix::net::UnixListener, path::Path, process::{exit, id}, thread::sleep, time::Duration, time::SystemTime, }; use sysinfo::{ProcessExt, System, SystemExt}; mod config; #[derive(PartialEq)] pub struct LastHotkey { hotkey: config::Hotkey, ran_at: SystemTime, } static STATE_MODE: OnceCell> = OnceCell::new(); static MODE_SOCK: &str = "/tmp/odilia-mode.sock"; fn listen_for_mode_change() -> std::io::Result<()> { log::trace!("Mode change listener started!"); let listener = UnixListener::bind(MODE_SOCK)?; match listener.accept() { Ok((mut socket, _addr)) => { let mut response = String::new(); socket.read_to_string(&mut response)?; log::trace!("reponse from odilia-mode.sock: '{}'", response); let mode_m = STATE_MODE.get(); match mode_m { Some(mm) => { let mut mode = mm.lock(); match mode { Ok(mut m) => { *m = response; println!("Mode updated to '{}'", m); log::trace!("Mode updated to '{}'", m); }, Err(e) => log::error!("Could not lock mode."), } }, _ => log::error!("Could not get static STATE_MODE."), } }, Err(e) => log::error!("accept function failed: {:?}", e), } Ok(()) } pub fn key_listener() { let args = set_flags().get_matches(); env::set_var("RUST_LOG", "swhkd=warn"); if args.is_present("debug") { env::set_var("RUST_LOG", "swhkd=trace"); } log::trace!("Logger initialized."); match STATE_MODE.set(Mutex::new(String::new())) { Err(e) => log::error!("could not initialize STATE_MODE"), _ => log::trace!("STATE_MODE initialized"), } let pidfile: String = String::from("/tmp/swhkd.pid"); if Path::new(&pidfile).exists() { log::trace!("Reading {} file and checking for running instances.", pidfile); let swhkd_pid = match fs::read_to_string(&pidfile) { Ok(swhkd_pid) => swhkd_pid, Err(e) => { log::error!("Unable to read {} to check all running instances", e); exit(1); } }; log::debug!("Previous PID: {}", swhkd_pid); let mut sys = System::new_all(); sys.refresh_all(); for (pid, process) in sys.processes() { if pid.to_string() == swhkd_pid && process.exe() == env::current_exe().unwrap() { log::error!("Swhkd is already running!"); exit(1); } } } match fs::write(&pidfile, id().to_string()) { Ok(_) => {} Err(e) => { log::error!("Unable to write to {}: {}", pidfile, e); exit(1); } } permission_check(); let config_file_path: std::path::PathBuf; if args.is_present("config") { config_file_path = Path::new(args.value_of("config").unwrap()).to_path_buf(); } else { config_file_path = check_config_xdg(); } log::debug!("Using config file path: {:#?}", config_file_path); if !config_file_path.exists() { log::error!("{:#?} doesn't exist", config_file_path); exit(1); } let hotkeys = match config::load(config_file_path) { Err(e) => { log::error!("Config Error: {}", e); exit(1); } Ok(out) => out, }; for hotkey in &hotkeys { log::debug!("hotkey: {:#?}", hotkey); } let modifiers_map: HashMap = HashMap::from([ (Key::MetaLeft, config::Modifier::Super), (Key::MetaRight, config::Modifier::Super), (Key::Alt, config::Modifier::Alt), (Key::AltGr, config::Modifier::Alt), (Key::ControlLeft, config::Modifier::Control), (Key::ControlRight, config::Modifier::Control), (Key::ShiftLeft, config::Modifier::Shift), (Key::ShiftRight, config::Modifier::Shift), ]); let mut possible_hotkeys: Vec = Vec::new(); let default_test_modifier: Vec = vec![config::Modifier::Super]; let mut last_hotkey = LastHotkey { // just a dummy last_hotkey so I don't need to mess with Option. TODO: Change this to Option hotkey: config::Hotkey::new( Some("".to_string()), Key::KeyA, default_test_modifier, String::from("notify-send \"it works\""), false, ), ran_at: SystemTime::now(), }; loop { // TODO } } pub fn main() { env_logger::init(); let key_handler = thread::spawn(|| { key_listener(); }); /* let mode_updater = thread::spawn(|| { let res = listen_for_mode_change(); match res { Ok(listener) => log::trace!("Finished mode changer!"), Err(e) => log::error!("Error setting up mode socket: {}", e), } }); */ log::trace!("Threads set up"); //mode_updater.join().unwrap(); key_handler.join().unwrap(); } pub fn permission_check() { if !Uid::current().is_root() { let groups = nix::unistd::getgroups(); for (_, groups) in groups.iter().enumerate() { for group in groups { let group = Group::from_gid(*group); if group.unwrap().unwrap().name == "input" { log::error!("Note: INVOKING USER IS IN INPUT GROUP!!!!"); log::error!("THIS IS A HUGE SECURITY RISK!!!!"); } } } log::error!("Consider using `pkexec swhkd ...`"); } else { log::warn!("Running swhkd as root!"); } } pub fn set_flags() -> App<'static> { let app = App::new("swhkd") .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) .about("Simple Wayland HotKey Daemon") .arg( arg!(-c --config ) .required(false) .takes_value(true) .help("Set a custom config file path."), ) .arg( arg!(-C --cooldown ) .required(false) .takes_value(true) .help("Set a custom repeat cooldown duration. Default is 250ms."), ) .arg(arg!(-d - -debug).required(false).help("Enable debug mode.")); app } pub fn check_config_xdg() -> std::path::PathBuf { let config_file_path: std::path::PathBuf; match env::var("XDG_CONFIG_HOME") { Ok(val) => { config_file_path = Path::new(&val).join("swhkd/swhkdrc"); log::debug!("XDG_CONFIG_HOME exists: {:#?}", val); return config_file_path; } Err(_) => { log::error!("XDG_CONFIG_HOME has not been set."); config_file_path = Path::new("/etc/swhkd/swhkdrc").to_path_buf(); log::warn!( "Note: Due to the design of the application, the invoking user is always root." ); log::warn!("You can set a custom config file with the -c option."); log::warn!("Adding your user to the input group could solve this."); log::warn!("However that's a massive security flaw and basically defeats the purpose of using wayland."); log::warn!("The following issue may be addressed in the future, but it is certainly not a priority right now."); } } config_file_path } fn sock_send(command: &str) -> std::io::Result<()> { let mut stream = UnixStream::connect("/tmp/swhkd.sock")?; stream.write_all(command.as_bytes())?; Ok(()) }