From 6092acc177f951b07f585164fb95d270a2f87e29 Mon Sep 17 00:00:00 2001 From: cyber-sushi Date: Sat, 18 May 2024 01:44:04 +0200 Subject: [PATCH] Wrap compositor status and active client into their own enum, split environment setup and task spawning --- src/udev_monitor.rs | 159 +++++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 69 deletions(-) diff --git a/src/udev_monitor.rs b/src/udev_monitor.rs index afeba50..7967803 100644 --- a/src/udev_monitor.rs +++ b/src/udev_monitor.rs @@ -2,20 +2,35 @@ use std::{collections::HashMap, sync::Arc, path::Path, process::Command, env}; use tokio::sync::Mutex; use tokio::task::JoinHandle; use tokio_stream::StreamExt; -use evdev::{Device, EventStream, Key}; +use evdev::{Device, EventStream}; use crate::Config; +use crate::config::Event; use crate::event_reader::EventReader; +#[derive(Eq, PartialEq, Hash, Clone)] +pub enum Client { + Default, + Class(String), +} + +#[derive(Clone)] +pub enum Server { + Connected(String), + Unsupported, + Failed, +} + #[derive(Clone)] pub struct Environment { pub user: Result, pub sudo_user: Result, - pub session_address: Result, + pub server: Server, } pub async fn start_monitoring_udev(config_files: Vec, mut tasks: Vec>) { - launch_tasks(&config_files, &mut tasks); + let environment = set_environment(); + launch_tasks(&config_files, &mut tasks, environment.clone()); let mut monitor = tokio_udev::AsyncMonitorSocket::new ( tokio_udev::MonitorBuilder::new().unwrap() .match_subsystem(std::ffi::OsStr::new("input")).unwrap() @@ -28,67 +43,14 @@ pub async fn start_monitoring_udev(config_files: Vec, mut tasks: Vec, tasks: &mut Vec>) { - let modifiers: Arc>> = Arc::new(Mutex::new(Default::default())); +pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>, environment: Environment) { + let modifiers: Arc>> = Arc::new(Mutex::new(Default::default())); let modifier_was_activated: Arc> = Arc::new(Mutex::new(true)); - match env::var("DBUS_SESSION_BUS_ADDRESS") { - Ok(_) => { - let command = Command::new("sh").arg("-c").arg("systemctl --user show-environment").output().unwrap(); - let vars = std::str::from_utf8(command.stdout.as_slice()).unwrap().split("\n").collect::>(); - for var in vars { - if var != "" && !var.contains("DBUS_SESSION_BUS_ADDRESS") { - let split_var = var.split("=").collect::>(); - env::set_var(split_var[0], split_var[1]); - } - } - true - }, - Err(_) => { - println!("Warning: unable to inherit user environment.\n\ - Launch Makima with 'sudo -E makima' or add the DBUS_SESSION_BUS_ADDRESS env var to your systemd unit if you're running it through systemd.\n"); - false - }, - }; - let environment = Environment { - user: env::var("USER"), - sudo_user: env::var("SUDO_USER"), - session_address: env::var("DBUS_SESSION_BUS_ADDRESS"), - }; - let mut session_var = "WAYLAND_DISPLAY"; - if let Err(env::VarError::NotPresent) = env::var(session_var) { - session_var = "XDG_SESSION_TYPE"; - } - let current_desktop: Option = match (env::var(session_var), env::var("XDG_CURRENT_DESKTOP")) { - (Ok(session), Ok(desktop)) if session.contains("wayland") && vec!["Hyprland".to_string(), "sway".to_string()].contains(&desktop) => { - println!("Running on {}, active window tracking enabled.", desktop); - Option::Some(desktop) - }, - (Ok(session), Ok(desktop)) if session.contains("wayland") => { - println!("Warning: unsupported compositor: {}, won't be able to change bindings according to active window.\n\ - Currently supported desktops: Hyprland, Sway, X11.\n", desktop); - Option::None - }, - (Ok(session), _) if session == "x11".to_string() => { - println!("Running on X11, active window tracking enabled."); - Option::Some("x11".to_string()) - }, - (Ok(session), Err(_)) if session.contains("wayland") => { - println!("Warning: unable to retrieve the current desktop based on XDG_CURRENT_DESKTOP env var.\n\ - Won't be able to change bindings according to the active window.\n"); - Option::None - }, - (Err(_), _) => { - println!("Warning: unable to retrieve the session type based on XDG_SESSION_TYPE or WAYLAND_DISPLAY env vars.\n\ - Won't be able to change bindings according to the active window.\n"); - Option::None - }, - _ => Option::None - }; let user_has_access = match Command::new("groups").output() { Ok(groups) if std::str::from_utf8(&groups.stdout.as_slice()).unwrap().contains("input") => { println!("Evdev permissions available.\nScanning for event devices with a matching config file...\n"); @@ -104,33 +66,33 @@ pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>) false }, Err(_) => { - println!("Unable to determine if user has access to event devices. Continuing...\n"); + println!("Warning: unable to determine if user has access to event devices. Continuing...\n"); false }, }; let devices: evdev::EnumerateDevices = evdev::enumerate(); let mut devices_found = 0; for device in devices { - let mut config_map: HashMap = HashMap::new(); + let mut config_map: HashMap = HashMap::new(); for config in config_files { let split_config_name = config.name.split("::").collect::>(); let associated_device_name = split_config_name[0]; if associated_device_name == device.1.name().unwrap().replace("/", "") { let window_class = if split_config_name.len() == 1 { - String::from("default") + Client::Default } else { - split_config_name[1].to_string() + Client::Class(split_config_name[1].to_string()) }; config_map.insert(window_class, config.clone()); }; } - if config_map.len() > 0 && !config_map.contains_key(&"default".to_string()) { - config_map.insert("default".to_string(), Config::new_empty(device.1.name().unwrap().replace("/", ""))); + if config_map.len() > 0 && !config_map.contains_key(&Client::Default) { + config_map.insert(Client::Default, Config::new_empty(device.1.name().unwrap().replace("/", ""))); } let event_device = device.0.as_path().to_str().unwrap().to_string(); if !config_map.is_empty() { let stream = Arc::new(Mutex::new(get_event_stream(Path::new(&event_device), config_map.clone()))); - let reader = EventReader::new(config_map.clone(), stream, modifiers.clone(), modifier_was_activated.clone(), environment.clone(), current_desktop.clone()); + let reader = EventReader::new(config_map.clone(), stream, modifiers.clone(), modifier_was_activated.clone(), environment.clone()); tasks.push(tokio::spawn(start_reader(reader))); devices_found += 1 } @@ -138,7 +100,7 @@ pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>) if devices_found == 0 && !user_has_access { println!("No matching devices found.\nNote: make sure that your user has access to event devices.\n"); } else if devices_found == 0 && user_has_access { - println!("No matching devices found.\nNote: double-check that your device and its respective config file have the same name, as reported by `evtest`.\n"); + println!("No matching devices found.\nNote: double-check that your device and its associated config file have the same name, as reported by 'evtest'.\n"); } } @@ -146,9 +108,68 @@ pub async fn start_reader(reader: EventReader) { reader.start().await; } -pub fn get_event_stream(path: &Path, config: HashMap) -> EventStream { +fn set_environment() -> Environment { + match env::var("DBUS_SESSION_BUS_ADDRESS") { + Ok(_) => { + let command = Command::new("sh").arg("-c").arg("systemctl --user show-environment").output().unwrap(); + let vars = std::str::from_utf8(command.stdout.as_slice()).unwrap().split("\n").collect::>(); + for var in vars { + if let Some((variable, value)) = var.split_once("=") { + env::set_var(variable, value); + } + } + true + }, + Err(_) => { + println!("Warning: unable to inherit user environment.\n\ + Launch Makima with 'sudo -E makima' or add the DBUS_SESSION_BUS_ADDRESS env var to your systemd unit if you're running it through systemd.\n"); + false + }, + }; + if let (Err(env::VarError::NotPresent), Ok(_)) = (env::var("XDG_SESSION_TYPE"), env::var("WAYLAND_DISPLAY")) { + env::set_var("XDG_SESSION_TYPE", "wayland") + } + + let supported_compositors = vec!["Hyprland", "sway"].into_iter().map(|str| String::from(str)).collect::>(); + let (x11, wayland) = (String::from("x11"), String::from("wayland")); + let server: Server = match (env::var("XDG_SESSION_TYPE"), env::var("XDG_CURRENT_DESKTOP")) { + (Ok(session), Ok(desktop)) if session == wayland && supported_compositors.contains(&desktop) => { + println!("Running on {}, per application bindings enabled.", desktop); + Server::Connected(desktop) + }, + (Ok(session), Ok(desktop)) if session == wayland => { + println!("Warning: unsupported compositor: {}, won't be able to change bindings according to the active window.\n\ + Currently supported desktops: Hyprland, Sway, X11.\n", desktop); + Server::Unsupported + }, + (Ok(session), _) if session == x11 => { + println!("Running on X11, per application bindings enabled."); + Server::Connected(session) + }, + (Ok(session), Err(_)) if session == wayland => { + println!("Warning: unable to retrieve the current desktop based on XDG_CURRENT_DESKTOP env var.\n\ + Won't be able to change bindings according to the active window.\n"); + Server::Unsupported + }, + (Err(_), _) => { + println!("Warning: unable to retrieve the session type based on XDG_SESSION_TYPE or WAYLAND_DISPLAY env vars.\n\ + Is your Wayland compositor or X server running?\n\ + Exiting Makima."); + std::process::exit(0); + }, + _ => Server::Failed + }; + + Environment { + user: env::var("USER"), + sudo_user: env::var("SUDO_USER"), + server, + } +} + +pub fn get_event_stream(path: &Path, config: HashMap) -> EventStream { let mut device: Device = Device::open(path).expect("Couldn't open device path."); - match config.get("default").unwrap().settings.get("GRAB_DEVICE") { + match config.get(&Client::Default).unwrap().settings.get("GRAB_DEVICE") { Some(value) => { if value == &true.to_string() { device.grab().expect("Unable to grab device. Is another instance of Makima running?")