xremap 0.15.2

Dynamic key remap for X and Wayland
Documentation
#![allow(dead_code)]

pub mod inputevent_formatter;
pub mod xremap_controller;

use crate::common::inputevent_formatter::get_pretty_events;
use anyhow::bail;
use evdev::uinput::VirtualDevice;
use evdev::{AttributeSet, BusType, Device, EventType, FetchEventsSynced, InputEvent, InputId, KeyCode, SwitchCode};
use nix::sys::select::{select, FdSet};
use nix::sys::time::TimeValLike;
use std::iter::repeat_with;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::time::Duration;
use xremap::util::{until, until_value};

pub fn key_click(key: KeyCode) -> Vec<InputEvent> {
    vec![key_press(key), key_release(key)]
}

pub fn key_release(key: KeyCode) -> InputEvent {
    InputEvent::new(EventType::KEY.0, key.code(), 0)
}

pub fn key_press(key: KeyCode) -> InputEvent {
    InputEvent::new(EventType::KEY.0, key.code(), 1)
}

pub fn get_raw_device_pair() -> anyhow::Result<(Device, VirtualDeviceInfo)> {
    let dev_info = get_virtual_device(get_random_device_name())?;

    let mut input = Device::open(&dev_info.path)?;

    input.grab()?;

    Ok((input, dev_info))
}

pub struct VirtualDeviceInfo {
    pub name: String,
    pub path: PathBuf,
    pub device: VirtualDevice,
}

pub fn get_random_device_name() -> String {
    format!("test device {}", repeat_with(fastrand::alphanumeric).take(10).collect::<String>())
}

pub fn get_virtual_device_without_wait(name: impl Into<String>) -> anyhow::Result<VirtualDevice> {
    let name = name.into();

    let mut keys: AttributeSet<KeyCode> = AttributeSet::new();
    for code in KeyCode::KEY_RESERVED.code()..KeyCode::BTN_TRIGGER_HAPPY40.code() {
        let key = KeyCode::new(code);
        let name = format!("{:?}", key);
        if name.starts_with("KEY_") || name.starts_with("BTN_") {
            keys.insert(key);
        }
    }

    let mut sw: AttributeSet<SwitchCode> = AttributeSet::new();

    sw.insert(SwitchCode::SW_LID);
    sw.insert(SwitchCode::SW_TABLET_MODE);

    let device = VirtualDevice::builder()?
        .input_id(InputId::new(BusType::BUS_USB, 0x1234, 0x5678, 0x111))
        .name(&name)
        .with_keys(&keys)?
        .with_switches(&sw)?
        .build()?;

    Ok(device)
}

pub fn get_virtual_device(name: impl Into<String>) -> anyhow::Result<VirtualDeviceInfo> {
    let name = name.into();
    let device = get_virtual_device_without_wait(&name)?;

    // Fetch path.
    let (path, _) = wait_for_device(&name)?;

    Ok(VirtualDeviceInfo { name, path, device })
}

pub fn wait_for_device(name: &str) -> anyhow::Result<(PathBuf, Device)> {
    until_value(
        || evdev::enumerate().find(|(_, device)| name == device.name().unwrap_or_default()),
        Duration::from_secs(1),
        &format!("Timed out waiting for device: {name}"),
    )
}

// Wait for the device to be grabbed by some other process.
pub fn wait_for_grabbed(path: &PathBuf) -> anyhow::Result<()> {
    until(
        || {
            let mut probe_device = Device::open(&path).unwrap();

            if probe_device.grab().is_err() {
                true
            } else {
                probe_device.ungrab().unwrap();
                false
            }
        },
        Duration::from_secs(1),
        &format!("Timed out waiting for device to be grabbed: {path:?}"),
    )
}

pub fn final_event_state(key: KeyCode, events: impl IntoIterator<Item = InputEvent>) -> Option<i32> {
    events.into_iter().fold(None, |state, ev| {
        if ev.event_type() == EventType::KEY && ev.code() == key.code() {
            if ev.value() == 0 {
                Some(0)
            } else {
                Some(1)
            }
        } else {
            state
        }
    })
}

pub fn assert_err<T, E>(expected: &str, result: Result<T, E>)
where
    E: ToString,
{
    match result {
        Ok(_) => panic!("\nExpected an error.\n"),
        Err(e) => {
            assert_eq!(expected, e.to_string());
        }
    }
}

pub fn assert_err_contains<T, E>(expected: &str, result: Result<T, E>)
where
    E: ToString,
{
    match result {
        Ok(_) => panic!("\nExpected an error.\n"),
        Err(e) => {
            if !e.to_string().contains(expected) {
                panic!("Should contain: {expected}\nError: {}", e.to_string());
            }
        }
    }
}

pub fn assert_str_contains(expected: &str, str: &str) {
    if !str.to_string().contains(expected) {
        panic!("Should contain: {expected}\n{str}");
    }
}

pub fn assert_events(actual: impl AsRef<Vec<InputEvent>>, expected: &str) {
    let actual = get_pretty_events(actual.as_ref());

    assert_eq!(actual, expected);
}

pub fn containsn(count: u64, hackstack: &str, needle: &str) -> bool {
    let mut hackstack = hackstack.to_string();

    for _ in 0..count {
        if !hackstack.contains(needle) {
            return false;
        }

        hackstack = hackstack.replacen(needle, "", 1);
    }

    !hackstack.contains(needle)
}

pub fn fetch_events(device: &mut Device) -> anyhow::Result<FetchEventsSynced<'_>> {
    let mut fds = FdSet::new();
    let fd = device.as_raw_fd();
    fds.insert(fd);

    select(None, &mut fds, None, None, Some(&mut TimeValLike::seconds(1)))?;

    if !fds.contains(fd) {
        bail!("Timed out waiting for xremap events.");
    }

    Ok(device.fetch_events()?)
}

#[test]
pub fn test_containsn() {
    assert!(containsn(0, "", "foo"));
    assert!(!containsn(1, "", "foo"));
    assert!(!containsn(2, "", "foo"));
    assert!(!containsn(0, "foo", "foo"));
    assert!(containsn(1, "foo", "foo"));
    assert!(!containsn(2, "foo", "foo"));
    assert!(!containsn(0, "foo foo", "foo"));
    assert!(!containsn(1, "foo foo", "foo"));
    assert!(containsn(2, "foo foo", "foo"));
}