~nickbp/nikau

56f52ec86400635d8b71ae14fa46825bf2b58438 — Nick Parker 6 months ago 0e7dbc1
Cut 0.3.0, bump deps, cargo fmt
M Cargo.lock => Cargo.lock +22 -22
@@ 76,9 76,9 @@ dependencies = [

[[package]]
name = "anyhow"
version = "1.0.76"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9"

[[package]]
name = "async-channel"


@@ 294,9 294,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "clap"
version = "4.4.11"
version = "4.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
dependencies = [
 "clap_builder",
 "clap_derive",


@@ 304,9 304,9 @@ dependencies = [

[[package]]
name = "clap_builder"
version = "4.4.11"
version = "4.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
dependencies = [
 "anstream",
 "anstyle",


@@ 379,9 379,9 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"

[[package]]
name = "crossbeam-channel"
version = "0.5.9"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5"
checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2"
dependencies = [
 "cfg-if",
 "crossbeam-utils",


@@ 389,9 389,9 @@ dependencies = [

[[package]]
name = "crossbeam-utils"
version = "0.8.17"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
 "cfg-if",
]


@@ 536,15 536,15 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"

[[package]]
name = "futures-core"
version = "0.3.29"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"

[[package]]
name = "futures-io"
version = "0.3.29"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"

[[package]]
name = "futures-lite"


@@ 950,9 950,9 @@ dependencies = [

[[package]]
name = "object"
version = "0.32.1"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
 "memchr",
]


@@ 1528,9 1528,9 @@ dependencies = [

[[package]]
name = "syn"
version = "2.0.42"
version = "2.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
dependencies = [
 "proc-macro2",
 "quote",


@@ 1545,18 1545,18 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"

[[package]]
name = "thiserror"
version = "1.0.51"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.51"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [
 "proc-macro2",
 "quote",

M src/device/handles.rs => src/device/handles.rs +11 -3
@@ 45,7 45,11 @@ pub struct DeviceHandles<H: DeviceHandler> {
}

impl<H: DeviceHandler> DeviceHandles<H> {
    pub fn new(handler: H, grab_tx: broadcast::Sender<device::GrabEvent>, all_combo_keys: HashSet<Key>) -> DeviceHandles<H> {
    pub fn new(
        handler: H,
        grab_tx: broadcast::Sender<device::GrabEvent>,
        all_combo_keys: HashSet<Key>,
    ) -> DeviceHandles<H> {
        DeviceHandles {
            always_grabbed_devices: HashMap::<PathBuf, DeviceHandle>::new(),
            toggled_devices: HashMap::<PathBuf, DeviceHandle>::new(),


@@ 60,7 64,10 @@ impl<H: DeviceHandler> DeviceHandles<H> {
        util::log_device_info(&device, &path, &device_info, "Listening to device", true);
        let supports_any_keys = supports_any_keys(&device, &self.all_combo_keys);
        if supports_any_keys {
            debug!("Device supports one or more configured combo keys: {}", device.name().unwrap_or("(Unnamed device)"));
            debug!(
                "Device supports one or more configured combo keys: {}",
                device.name().unwrap_or("(Unnamed device)")
            );
        }
        if supports_any_keys {
            // This device supports one or more keys configured for client switch key combinations.


@@ 70,7 77,8 @@ impl<H: DeviceHandler> DeviceHandles<H> {
                None,
                device_info,
            )?;
            self.always_grabbed_devices.insert(path.clone(), join_handle);
            self.always_grabbed_devices
                .insert(path.clone(), join_handle);
        } else {
            // This device doesn't support keys used in key combinations (e.g. a mouse).
            // When the server is the active input, we can ungrab the device,

M src/device/input.rs => src/device/input.rs +44 -17
@@ 89,9 89,7 @@ impl DeviceHandler for InputHandler {
        } else {
            // Device is to be permanently grabbed
            handle_grab_event(&mut stream, &mut device_info, GrabEvent::Grab);
            task::spawn(async move {
                read_device_events(&mut stream, config, device_info).await
            })
            task::spawn(async move { read_device_events(&mut stream, config, device_info).await })
        };
        Ok(DeviceHandle { handle })
    }


@@ 107,7 105,15 @@ async fn read_device_events(
    loop {
        match stream.next_event().await {
            Ok(event) => {
                handle_input_event(stream, &mut handler_config, event, &device_info, &mut input_events_batch, &mut combo_events_batch).await
                handle_input_event(
                    stream,
                    &mut handler_config,
                    event,
                    &device_info,
                    &mut input_events_batch,
                    &mut combo_events_batch,
                )
                .await
            }
            Err(e) => {
                // Common when the device has been unplugged.


@@ 183,7 189,8 @@ async fn handle_input_event(
    // 100 limit: Just in case, avoid the risk of collecting queued events forever.
    //            In practice we should only be collecting 2-3 events between syncs.
    if event.event_type() == EventType::SYNCHRONIZATION
        || (input_events_batch.len() + combo_events_batch.len()) >= 100 {
        || (input_events_batch.len() + combo_events_batch.len()) >= 100
    {
        // Flush events to be handled by the client as a group
        if !input_events_batch.is_empty() {
            let event = Event::Input(InputBatch {


@@ 212,8 219,7 @@ async fn handle_input_event(
                shortcut::ComboAction::ConsumeEvent => {
                    any_consume = true;
                }
                shortcut::ComboAction::PassEvent => {
                }
                shortcut::ComboAction::PassEvent => {}
                shortcut::ComboAction::ConsumeEventAndEmitAction(action) => {
                    any_consume = true;
                    combo_events_batch.push(action);


@@ 224,32 230,53 @@ async fn handle_input_event(
            }
        }
        if any_consume {
            debug!("Dropping key event as it's the last key completing one or more combos: {:?}", event);
            debug!(
                "Dropping key event as it's the last key completing one or more combos: {:?}",
                event
            );
        } else {
            input_events_batch.push(convert_device_event(event, stream.device(), device_info))
        }
    }
}

fn handle_grab_event(stream: &mut EventStream, device_info: &mut util::DeviceInfo, grab: GrabEvent) -> bool {
fn handle_grab_event(
    stream: &mut EventStream,
    device_info: &mut util::DeviceInfo,
    grab: GrabEvent,
) -> bool {
    match grab {
        GrabEvent::Grab => {
            debug!("Grabbing device: {:?}", stream.device().name().unwrap_or("(Unnamed device)"));
            debug!(
                "Grabbing device: {:?}",
                stream.device().name().unwrap_or("(Unnamed device)")
            );
            if let Err(e) = stream.device_mut().grab() {
                warn!("Failed to grab device {:?}, removing device: {}", stream.device().name(), e);
                return false
                warn!(
                    "Failed to grab device {:?}, removing device: {}",
                    stream.device().name(),
                    e
                );
                return false;
            }
            device_info.is_grabbed = true;
            return true
            return true;
        }
        GrabEvent::Ungrab => {
            debug!("Ungrabbing device: {:?}", stream.device().name().unwrap_or("(Unnamed device)"));
            debug!(
                "Ungrabbing device: {:?}",
                stream.device().name().unwrap_or("(Unnamed device)")
            );
            if let Err(e) = stream.device_mut().ungrab() {
                warn!("Failed to ungrab device {:?}, : {}", stream.device().name(), e);
                return false
                warn!(
                    "Failed to ungrab device {:?}, : {}",
                    stream.device().name(),
                    e
                );
                return false;
            }
            device_info.is_grabbed = false;
            return true
            return true;
        }
    }
}

M src/device/output/uinput.rs => src/device/output/uinput.rs +38 -11
@@ 1,8 1,8 @@
use anyhow::{Context, Result};
use async_trait::async_trait;
use evdev::{
    uinput, AbsInfo, AbsoluteAxisType, AttributeSet, EvdevEnum, InputEventKind, Key,
    MiscType, RelativeAxisType,
    uinput, AbsInfo, AbsoluteAxisType, AttributeSet, EvdevEnum, InputEventKind, Key, MiscType,
    RelativeAxisType,
};
use tracing::{debug, info, trace, warn};



@@ 218,29 218,47 @@ impl OutputHandler for VirtualUInputDevices {
        // The events should be single-device in most cases, but we support mixed events too, just in case.
        if keyboard_count == events.len() {
            // All of the events can be classified as keyboard
            let events = events.iter().map(|e| e.0).collect::<Vec<evdev::InputEvent>>();
            let events = events
                .iter()
                .map(|e| e.0)
                .collect::<Vec<evdev::InputEvent>>();
            trace!(
                "Emitting {} keyboard events: {:?}",
                events.len(),
                events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>()
                events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>()
            );
            self.keyboard_device.emit(&events)?;
        } else if mouse_count == events.len() {
            // All of the events can be classified as mouse
            let events = events.iter().map(|e| e.0).collect::<Vec<evdev::InputEvent>>();
            let events = events
                .iter()
                .map(|e| e.0)
                .collect::<Vec<evdev::InputEvent>>();
            trace!(
                "Emitting {} mouse events: {:?}",
                events.len(),
                events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>()
                events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>()
            );
            self.mouse_device.emit(&events)?;
        } else if touchpad_count == events.len() {
            // All of the events can be classified as touchpad
            let events = events.iter().map(|e| e.0).collect::<Vec<evdev::InputEvent>>();
            let events = events
                .iter()
                .map(|e| e.0)
                .collect::<Vec<evdev::InputEvent>>();
            trace!(
                "Emitting {} touchpad events: {:?}",
                events.len(),
                events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>()
                events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>()
            );
            self.touchpad_device.emit(&events)?;
        } else {


@@ 284,11 302,20 @@ impl OutputHandler for VirtualUInputDevices {
            trace!(
                "Emitting events: keyboard({})={:?} mouse({})={:?} touchpad({})={:?}",
                keyboard_events.len(),
                keyboard_events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>(),
                keyboard_events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>(),
                mouse_events.len(),
                mouse_events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>(),
                mouse_events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>(),
                touchpad_events.len(),
                touchpad_events.iter().map(|e| util::log_event(e)).collect::<Vec<String>>(),
                touchpad_events
                    .iter()
                    .map(|e| util::log_event(e))
                    .collect::<Vec<String>>(),
            );
            if !keyboard_events.is_empty() {
                info!("emit keeb: {:?}", keyboard_events);

M src/device/util.rs => src/device/util.rs +11 -2
@@ 90,7 90,13 @@ impl DeviceInfo {
    }
}

pub fn log_device_info(device: &Device, path: &Path, device_info: &DeviceInfo, log_prefix: &str, info: bool) {
pub fn log_device_info(
    device: &Device,
    path: &Path,
    device_info: &DeviceInfo,
    log_prefix: &str,
    info: bool,
) {
    // under info, show device name/path only
    let msg = format!(
        "{}: {} @ {}",


@@ 104,7 110,10 @@ pub fn log_device_info(device: &Device, path: &Path, device_info: &DeviceInfo, l
        debug!("{}", msg);
    }
    // under debug, show nikau version of device details
    debug!("Nikau device details:{}", device_info_string(device, &device_info.dims));
    debug!(
        "Nikau device details:{}",
        device_info_string(device, &device_info.dims)
    );
    // under trace, show evdev version of things too, but note that the abs values are missing:
    trace!("Evdev device details:\n{}", device);
}

M src/device/watch.rs => src/device/watch.rs +13 -2
@@ 164,7 164,13 @@ fn compatible_device(d: &Device, path: &Path, device_info: &util::DeviceInfo) ->
                .all(|key| key == Key::KEY_POWER || key == Key::KEY_SLEEP || key == Key::KEY_WAKEUP)
        } else {
            // Key device without any keys? Skip it
            util::log_device_info(d, path, device_info, "Ignoring KEY device lacking supported keys", false);
            util::log_device_info(
                d,
                path,
                device_info,
                "Ignoring KEY device lacking supported keys",
                false,
            );
            false
        }
    } else {


@@ 180,7 186,12 @@ fn compatible_device(d: &Device, path: &Path, device_info: &util::DeviceInfo) ->
    }
}

fn matches_filters(name_filters: &Vec<Regex>, device: &Device, path: &Path, device_info: &util::DeviceInfo) -> bool {
fn matches_filters(
    name_filters: &Vec<Regex>,
    device: &Device,
    path: &Path,
    device_info: &util::DeviceInfo,
) -> bool {
    let device_name = device.name().unwrap_or("(Unnamed device)");
    if name_filters.is_empty() {
        return true;

M src/main.rs => src/main.rs +2 -1
@@ 231,7 231,8 @@ async fn server(
    let input_handler = input::InputHandler::new(&key_combos, event_tx)?;

    let watch_handle = task::spawn(async move {
        let device_handles = handles::DeviceHandles::new(input_handler, grab_tx, key_combos.all_keys);
        let device_handles =
            handles::DeviceHandles::new(input_handler, grab_tx, key_combos.all_keys);
        watch::watch_loop(device_handles, device_filters)
            .await
            .context(

M src/rotation.rs => src/rotation.rs +2 -1
@@ 870,7 870,8 @@ impl<O: device::output::OutputHandler> Rotation<O> {
    pub async fn send_input_events(&mut self, batch: device::InputBatch) -> Result<()> {
        if let Some(_) = self.current_client {
            // Remote client is active, send all input to client and not to local machine.
            self.send_event_to_remote_client(event::ServerEvent::Input(batch.events)).await
            self.send_event_to_remote_client(event::ServerEvent::Input(batch.events))
                .await
        } else if batch.is_grabbed {
            // Local machine is active and device is grabbed, write input to local virtual devices.
            // For example, we grab keyboards so that we can skip sending switch combos to the local system.