diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2dcd79c --- /dev/null +++ b/src/config.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; +use evdev::Key; +use serde; + + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct Config { + #[serde(skip)] + pub name: String, + #[serde(default)] + pub keys: HashMap>, + pub settings: HashMap, + #[serde(skip)] + pub abs: HashMap>, + #[serde(default)] + pub rel: HashMap>, +} + +impl Config { + pub fn new_from_file(file: &str, file_name: String) -> Self { + println!("Parsing config file at {:?}", file); + let file_content: String = std::fs::read_to_string(file).unwrap(); + let config: Config = toml::from_str(&file_content) + .expect("Couldn't parse config file."); + let keys: HashMap> = config.keys; + let rel: HashMap> = config.rel; + let mut abs: HashMap> = HashMap::new(); + let mut pad_horizontal: Vec = keys.get(&Key::BTN_DPAD_LEFT) + .unwrap_or(&Vec::new()).clone(); + pad_horizontal.extend(keys.get(&Key::BTN_DPAD_RIGHT) + .unwrap_or(&Vec::new())); + let mut pad_vertical: Vec = keys.get(&Key::BTN_DPAD_UP) + .unwrap_or(&Vec::new()).clone(); + pad_vertical.extend(keys.get(&Key::BTN_DPAD_DOWN) + .unwrap_or(&Vec::new())); + abs.insert("NONE_X".to_string(), pad_horizontal); + abs.insert("NONE_Y".to_string(), pad_vertical); + let settings: HashMap = config.settings; + Self { + name: file_name, + keys: keys, + settings: settings, + abs: abs, + rel: rel + } + } +} + diff --git a/src/event_reader.rs b/src/event_reader.rs new file mode 100644 index 0000000..3188430 --- /dev/null +++ b/src/event_reader.rs @@ -0,0 +1,238 @@ +use std::{collections::HashMap, sync::Arc, option::Option, env}; +use tokio::sync::Mutex; +use tokio_stream::StreamExt; +use evdev::{EventStream, Key, RelativeAxisType, AbsoluteAxisType, EventType, InputEvent}; +use hyprland::{data::Client, prelude::*}; +use swayipc_async::Connection; +use crate::virtual_devices::VirtualDevices; +use crate::Config; + + +pub struct EventReader { + config: HashMap, + stream: Arc>, + virt_dev: Arc>, + analog_position: Arc>>, + device_is_connected: Arc>, + current_desktop: Option, +} + +impl EventReader { + pub fn new(config: HashMap, stream: Arc>, virt_dev: Arc>) -> Self { + let mut position_vector: Vec = Vec::new(); + for i in [0, 0] {position_vector.push(i)}; + let position_vector_mutex = Arc::new(Mutex::new(position_vector)); + let device_is_connected: Arc> = Arc::new(Mutex::new(true)); + let current_desktop: Option = match env::var("XDG_CURRENT_DESKTOP") { + Ok(desktop) if vec!["Hyprland".to_string(), "sway".to_string()].contains(&desktop) => { + println!("Running on {}, active window detection enabled.", desktop); + Option::Some(desktop) + }, + Ok(desktop) => { + println!("Unsupported desktop: {}, won't be able to change bindings according to active window.\n + Currently supported desktops: Hyprland", desktop); + Option::None + }, + Err(_) => { + println!("Unable to retrieve current desktop based on XDG_CURRENT_DESKTOP env var.\n + Won't be able to change bindings according to active window."); + Option::None + }, + }; + Self { + config: config, + stream: stream, + virt_dev: virt_dev, + analog_position: position_vector_mutex, + device_is_connected: device_is_connected, + current_desktop: current_desktop, + } + } + + pub async fn start(&self) { + let mut stream = self.stream.lock().await; + let mut analog_mode: &str = "left"; + if let Some(stick) = self.config.get(&self.get_active_window().await).unwrap().settings.get("POINTER_STICK") { + analog_mode = stick.as_str(); + } + let mut has_signed_axis_value: &str = "false"; + if let Some(axis_value) = self.config.get(&self.get_active_window().await).unwrap().settings.get("SIGNED_AXIS_VALUE") { + has_signed_axis_value = axis_value.as_str(); + } + while let Some(Ok(event)) = stream.next().await { + match (event.event_type(), RelativeAxisType(event.code()), AbsoluteAxisType(event.code()), analog_mode) { + (EventType::KEY, _, _, _) => { + if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key(event.code())) { + self.emit_event(event_list, event.value()).await + } else { + self.emit_default_event(event).await; + } + }, + (_, RelativeAxisType::REL_WHEEL | RelativeAxisType::REL_WHEEL_HI_RES, _, _) => { + let event_list_option: Option<&Vec> = match event.value() { + -1 => self.config.get(&self.get_active_window().await).unwrap().rel.get(&"SCROLL_WHEEL_DOWN".to_string()), + 1 => self.config.get(&self.get_active_window().await).unwrap().rel.get(&"SCROLL_WHEEL_UP".to_string()), + _ => None, + }; + if let Some(event_list) = event_list_option { + self.emit_event(event_list, event.value()).await; + self.emit_event(event_list, 0).await; + } else { + if !self.config.get(&self.get_active_window().await).unwrap().rel.contains_key("SCROLL_WHEEL_DOWN") + && !self.config.get(&self.get_active_window().await).unwrap().rel.contains_key("SCROLL_WHEEL_UP") { + self.emit_default_event(event).await; + } + } + }, + (_, _, AbsoluteAxisType::ABS_HAT0X, _) => { + let event_list_option: Option<&Vec> = match event.value() { + -1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_LEFT), + 0 => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_X".to_string()), + 1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_RIGHT), + _ => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_X".to_string()), + }; + if let Some(event_list) = event_list_option { + self.emit_event(event_list, event.value()).await; + } else { + println!("Button not set in the config file!"); + } + }, + (_, _, AbsoluteAxisType::ABS_HAT0Y, _) => { + let event_list_option: Option<&Vec> = match event.value() { + -1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_UP), + 0 => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_Y".to_string()), + 1 => self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_DPAD_DOWN), + _ => self.config.get(&self.get_active_window().await).unwrap().abs.get(&"NONE_Y".to_string()), + }; + if let Some(event_list) = event_list_option { + self.emit_event(event_list, event.value()).await; + } else { + println!("Button not set in the config file!"); + } + }, + (EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_X | AbsoluteAxisType::ABS_Y, "left") => { + let rel_value = self.get_rel_value(&has_signed_axis_value, &event).await; + let mut analog_position = self.analog_position.lock().await; + analog_position[event.code() as usize] = rel_value; + }, + (EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_RX | AbsoluteAxisType::ABS_RY, "right") => { + let rel_value = self.get_rel_value(&has_signed_axis_value, &event).await; + let mut analog_position = self.analog_position.lock().await; + analog_position[(event.code() as usize) -3] = rel_value; + }, + (EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_Z, _) => { + if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_TL2) { + if event.value() == 0 { + self.emit_event(event_list, event.value()).await + } else { + self.emit_event(event_list, 1).await + }; + } else { + println!("Button not set in the config file!"); + }; + }, + (EventType::ABSOLUTE, _, AbsoluteAxisType::ABS_RZ, _) => { + if let Some(event_list) = self.config.get(&self.get_active_window().await).unwrap().keys.get(&Key::BTN_TR2) { + if event.value() == 0 { + self.emit_event(event_list, event.value()).await + } else { + self.emit_event(event_list, 1).await + }; + } else { + println!("Button not set in the config file!"); + }; + }, + _ => {self.emit_default_event(event).await} + } + } + let mut device_is_connected = self.device_is_connected.lock().await; + *device_is_connected = false; + } + + async fn emit_event(&self, event_list: &Vec, value: i32) { + for key in event_list { + let virtual_event: InputEvent = InputEvent::new_now(EventType::KEY, key.code(), value); + let mut virt_dev = self.virt_dev.lock().await; + virt_dev.keys.emit(&[virtual_event]).unwrap(); + } + } + + async fn emit_default_event(&self, event: InputEvent) { + let mut virt_dev = self.virt_dev.lock().await; + match event.event_type() { + EventType::KEY => virt_dev.keys.emit(&[event]).unwrap(), + EventType::RELATIVE => virt_dev.relative_axes.emit(&[event]).unwrap(), + _ => {} + } + } + + async fn get_rel_value(&self, has_signed_axis_value: &str, event: &InputEvent) -> i32 { + let rel_value: i32 = match &has_signed_axis_value { + &"false" => { + let distance_from_center: i32 = event.value() as i32 - 128; + distance_from_center / 10 + } + _ => { + event.value() as i32 / 2000 + } + }; + return rel_value + } + + pub async fn cursor_loop(&self) { + if let Some(sensitivity) = self.config.get(&self.get_active_window().await).unwrap().settings.get("ANALOG_SENSITIVITY") { + let polling_rate: u64 = sensitivity.parse::().expect("Invalid analog sensitivity."); + while *self.device_is_connected.lock().await { + { + let analog_position = self.analog_position.lock().await; + if analog_position[0] != 0 || analog_position[1] != 0 { + let virtual_event_x: InputEvent = InputEvent::new_now(EventType::RELATIVE, 0, analog_position[0]); + let virtual_event_y: InputEvent = InputEvent::new_now(EventType::RELATIVE, 1, analog_position[1]); + let mut virt_dev = self.virt_dev.lock().await; + virt_dev.relative_axes.emit(&[virtual_event_x]).unwrap(); + virt_dev.relative_axes.emit(&[virtual_event_y]).unwrap(); + } + } + tokio::time::sleep(std::time::Duration::from_millis(polling_rate)).await; + } + } else { + return + } + } + + async fn get_active_window(&self) -> String { + let active_client = self.current_desktop.clone().unwrap_or(String::from("default")); + match active_client.as_str() { + "Hyprland" => { + let active_window: String = match Client::get_active_async().await.unwrap() { + Some(window) => window.class, + None => String::from("default") + }; + if self.config.contains_key(&active_window) { + active_window + } else { + String::from("default") + } + }, + "sway" => { + let mut connection = Connection::new().await.unwrap(); + let active_window = match connection.get_tree().await.unwrap().find_focused(|window| window.focused) { + Some(window) => { + match window.app_id { + Some(id) => id, + None => window.window_properties.unwrap().class.unwrap() + } + }, + None => String::from("default") + }; + if self.config.contains_key(&active_window) { + active_window + } else { + String::from("default") + } + }, + _ => String::from("default") + } + } +} + diff --git a/src/udev_monitor.rs b/src/udev_monitor.rs new file mode 100644 index 0000000..0431492 --- /dev/null +++ b/src/udev_monitor.rs @@ -0,0 +1,129 @@ +use std::{collections::HashMap, sync::Arc, path::Path}; +use tokio::sync::Mutex; +use tokio::task::JoinHandle; +use tokio_stream::StreamExt; +use evdev::{Device, EventStream, Key, uinput::VirtualDeviceBuilder}; +use crate::Config; +use crate::event_reader::EventReader; +use crate::virtual_devices::VirtualDevices; + + +pub async fn start_monitoring_udev(config_files: Vec, mut tasks: Vec>) { + launch_tasks(&config_files, &mut tasks); + let mut monitor = tokio_udev::AsyncMonitorSocket::new( + tokio_udev::MonitorBuilder::new().unwrap() + .match_subsystem(std::ffi::OsStr::new("input")).unwrap() + .listen().unwrap() + ).unwrap(); + while let Some(Ok(event)) = monitor.next().await { + if is_mapped(&event.device(), &config_files) { + println!("Reinitializing..."); + for task in &tasks { + task.abort(); + } + tasks.clear(); + launch_tasks(&config_files, &mut tasks) + } + } +} + +pub fn launch_tasks(config_files: &Vec, tasks: &mut Vec>) { + let devices: evdev::EnumerateDevices = evdev::enumerate(); + for device in devices { + 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() { + let window_class = if split_config_name.len() == 1 { + String::from("default") + } else { + split_config_name[1].to_string() + }; + config_map.insert(window_class, config.clone()); + }; + } + if !config_map.is_empty() { + tasks.push( + tokio::spawn( + create_new_reader( + device.0.as_path().to_str().unwrap().to_string(), + config_map.clone() + ) + ) + ) + } + } +} + +pub async fn create_new_reader(device: String, config: HashMap) { + let stream: Arc> = Arc::new ( + Mutex::new ( + get_event_stream ( + Path::new(&device), + config.clone() + ) + ) + ); + let virt_dev: Arc> = Arc::new ( + Mutex::new(new_virtual_devices()) + ); + let reader = EventReader::new(config.clone(), stream, virt_dev); + println!("Mapped device detected at {}, reading events.", device); + tokio::join!( + reader.start(), + reader.cursor_loop(), + ); + println!("Disconnected device at {}.", device); +} + +pub fn get_event_stream(path: &Path, config: HashMap) -> EventStream { + let mut device: Device = Device::open(path).expect("Couldn't open device path."); + if config.get("default") + .unwrap() + .settings + .get("GRAB_DEVICE") + .expect("No GRAB_DEVICE setting specified, this device will be ignored.") == &"true".to_string() + { + device.grab().unwrap(); + }; + + let stream: EventStream = device.into_event_stream().unwrap(); + return stream +} + +pub fn new_virtual_devices() -> VirtualDevices { + let mut key_capabilities = evdev::AttributeSet::new(); + for i in 1..334 {key_capabilities.insert(Key(i));}; + let mut rel_capabilities = evdev::AttributeSet::new(); + for i in 0..13 {rel_capabilities.insert(evdev::RelativeAxisType(i));}; + let keys_builder = VirtualDeviceBuilder::new().unwrap() + .name("Makima Virtual Keyboard/Mouse") + .with_keys(&key_capabilities).unwrap(); + let rel_builder = VirtualDeviceBuilder::new().unwrap() + .name("Makima Virtual Pointer") + .with_relative_axes(&rel_capabilities).unwrap(); + let virtual_device_keys = keys_builder.build().unwrap(); + let virtual_device_rel = rel_builder.build().unwrap(); + let virtual_devices = VirtualDevices::new(virtual_device_keys, virtual_device_rel); + return virtual_devices; +} + +pub fn is_mapped(udev_device: &tokio_udev::Device, config_files: &Vec) -> bool { + match udev_device.devnode() { + Some(devnode) => { + let evdev_devices: evdev::EnumerateDevices = evdev::enumerate(); + for evdev_device in evdev_devices { + for config in config_files { + if config.name.contains(&evdev_device.1.name().unwrap().to_string()) + && devnode.to_path_buf() == evdev_device.0 { + return true + } + } + } + } + _ => return false + } + return false +} + diff --git a/src/virtual_devices.rs b/src/virtual_devices.rs new file mode 100644 index 0000000..f4b8ee6 --- /dev/null +++ b/src/virtual_devices.rs @@ -0,0 +1,17 @@ +use evdev::uinput::VirtualDevice; + + +pub struct VirtualDevices { + pub keys: VirtualDevice, + pub relative_axes: VirtualDevice, +} + +impl VirtualDevices { + pub fn new(keys: VirtualDevice, relative_axes: VirtualDevice) -> Self { + Self { + keys: keys, + relative_axes: relative_axes, + } + } +} +