xremap 0.15.3

Dynamic key remap for X and Wayland
Documentation
#![cfg(feature = "device-test")]

use crate::common::xremap_controller::{InputDeviceFilter, XremapController};
use crate::common::{
    assert_err, assert_events, assert_str_contains, fetch_events, get_random_device_name, get_raw_device_pair,
    get_virtual_device, key_press, key_release, wait_for_device,
};
use anyhow::Result;
use evdev::uinput::VirtualDevice;
use evdev::{Device, KeyCode as Key};
use indoc::indoc;
use std::time::Duration;
use xremap::device::select_input_devices;

mod common;

#[test]
pub fn test_no_input_device_match() {
    let device_filter = vec!["match_nothing".into()];

    assert_err(
        "Failed to prepare input devices: No device was selected!",
        select_input_devices(&device_filter, &vec![], false, false, "own_device"),
    );
}

#[test]
pub fn test_device_without_keys_is_not_selected_automatically() -> Result<()> {
    // Create device without any output events.
    let name = get_random_device_name();
    let _device = VirtualDevice::builder()?.name(&name).build()?;
    let _ = wait_for_device(&name)?;

    // Automatically select devices
    let devices = select_input_devices(&[], &vec![], false, false, "own_device")?;

    assert_eq!(
        0,
        devices
            .iter()
            .filter(|(_, device)| device.device_name() == name)
            .count()
    );

    Ok(())
}

#[test]
pub fn test_device_filter_overwrites_keyboard_and_mouse_check() -> Result<()> {
    // Create device, that will not be selected automatically.
    let name = get_random_device_name();
    let _device = VirtualDevice::builder()?.name(&name).build()?;
    let _ = wait_for_device(&name)?;

    // Selects the device, because filter overwrites the automatic selection rules.
    let names: Vec<String> = select_input_devices(&[name.clone()], &vec![], false, false, "own_device")?
        .iter()
        .map(|(_, device)| device.device_name().to_string())
        .collect();

    assert_eq!(vec![name], names);

    Ok(())
}

#[test]
pub fn test_device_that_does_not_exist() -> Result<()> {
    // The device path doesn't exist, so will not cause other errors than no devices selected
    let ctrl = XremapController::builder()
        .allow_stdio_errors(true)
        .not_open_for_fetch()
        .input_device(InputDeviceFilter::CustomFilter {
            filter: "/dev/input/event99".into(),
        })
        .build()?;

    let output = ctrl.wait_for_output()?;

    assert_str_contains("Failed to prepare input devices", &output.stderr);

    Ok(())
}

#[test]
pub fn test_device_that_is_already_grabbed() -> Result<()> {
    let (mut input, output) = get_raw_device_pair()?;

    let mut ctrl = XremapController::builder()
        .allow_stdio_errors(true)
        .not_open_for_fetch()
        .input_device(InputDeviceFilter::CustomFilter {
            filter: output.path.to_string_lossy().into(),
        })
        .build()?;

    std::thread::sleep(Duration::from_millis(1000));

    input.ungrab()?;

    let output = ctrl.kill_for_output()?;

    assert_str_contains("Failed to prepare input devices", &output.stderr);
    assert_str_contains("Device or resource busy", &output.stderr);
    assert_str_contains("Another program might have grabbed the device", &output.stderr);

    Ok(())
}

#[test]
pub fn test_last_device_removed_in_non_watch_mode() -> Result<()> {
    let mut ctrl = XremapController::builder().allow_stdio_errors(true).build()?;

    ctrl.close_input_device()?;

    // Can wait because xremap exits in this case
    let output = ctrl.wait_for_output()?;

    assert_str_contains("Last device was removed, and not watching for new devices", &output.stderr);

    Ok(())
}

#[test]
pub fn test_wait_for_all_up_keys_up() -> Result<()> {
    let name = get_random_device_name();
    let mut device = get_virtual_device(&name)?;
    let mut input = Device::open(&device.path)?; // Open without grabbing

    // A key that likely has no effect
    device.device.emit(&[key_press(Key::BTN_TRIGGER_HAPPY1)])?;

    let ctrl = XremapController::builder()
        .not_open_for_fetch()
        .input_device(InputDeviceFilter::CustomFilter { filter: name.clone() })
        .build()?;

    // Allow xremap to go into wait-for-keys-loop
    std::thread::sleep(Duration::from_millis(100));
    device.device.emit(&[key_release(Key::BTN_TRIGGER_HAPPY1)])?;

    // Allow xremap to grab
    std::thread::sleep(Duration::from_millis(200));

    // The release event was not swallowed by xremap
    let events: Vec<_> = fetch_events(&mut input)?.collect();
    assert_events(
        events,
        indoc! {"
            btn_trigger_happy1:1
            btn_trigger_happy1:0
        "},
    );

    ctrl.kill()
}

#[test]
pub fn test_wait_for_all_up_keys_up_fails() -> Result<()> {
    let name = get_random_device_name();
    let mut device = get_virtual_device(&name)?;

    // A key that likely has no effect
    device.device.emit(&[key_press(Key::BTN_TRIGGER_HAPPY1)])?;

    let ctrl = XremapController::builder()
        .allow_stdio_errors(true)
        .not_open_for_fetch()
        .input_device(InputDeviceFilter::CustomFilter { filter: name.clone() })
        .build()?;

    std::thread::sleep(Duration::from_millis(500));

    let output = ctrl.wait_for_output()?;

    assert_str_contains("Failed to prepare input devices", &output.stderr);
    assert_str_contains("Timed out waiting for keys to be released", &output.stderr);

    Ok(())
}