apeirogond 0.37.0

Daemon for Apeirogon, a cross-platform software KVM
// SPDX-License-Identifier: GPL-3.0-or-later

use std::net::{Ipv4Addr, SocketAddr};

use a7n_core::layout::Placement;
use tempfile::TempDir;

use super::*;
use crate::pin_store::Entry;

fn placed_daemon(target_y: i32) -> (Daemon, TempDir, [MachineId; 3]) {
    let dir = tempfile::tempdir().unwrap();
    let mut daemon = Daemon::load(dir.path(), SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).unwrap();
    let own = daemon.identity.machine_id;
    let target = MachineId::try_from(&[42; MachineId::LEN][..]).unwrap();
    let later = MachineId::try_from(&[43; MachineId::LEN][..]).unwrap();
    for id in [target, later] {
        daemon.pins.add(Entry {
            machine_id: id,
            pin: daemon.identity.pin,
            name: "peer".into(),
            spki_der: None,
            hw_addr: Some([2; 6]),
            screen_mm: None,
        });
    }
    let placements = [(own, 0, 0), (target, 100, target_y), (later, 200, 0)]
        .map(|(id, x, y)| Placement {
            id,
            x,
            y,
            width: 100,
            height: 100,
            blocked: 0,
        })
        .to_vec();
    daemon.layout.state = daemon.layout.state.edit(placements, own).unwrap();
    (daemon, dir, [own, target, later])
}

#[tokio::test]
async fn crossing_exit_unpaired_target_returns_no_crossing() {
    // Arrange
    for target_y in [0, 100] {
        let (mut daemon, _dir, [_, target, _]) = placed_daemon(target_y);
        daemon.unpair(target).unwrap();

        // Act
        let crossing = daemon.crossing_exit(
            (0, 0, 100, 100),
            &[(0, 0, 100, 100).into()],
            (99, 98),
            (100, 1),
            (Edge::Right, 0.98),
        );

        // Assert
        assert!(crossing.is_none());
    }
}

#[tokio::test]
async fn cross_edge_outside_left_point_reaches_paired_target() {
    // Arrange
    let (mut daemon, _dir, [own, target, _]) = placed_daemon(0);
    let mut placements = daemon.layout.state.placements().to_vec();
    placements[1].x = -100;
    daemon.layout.state = daemon.layout.state.edit(placements, own).unwrap();
    daemon.wol.sender = Box::new(|_| Ok(()));
    let bounds = Bounds {
        x: 0,
        y: 0,
        width: 100,
        height: 100,
    };
    let dx = -i32::try_from(a7n_proto::limits::DEFAULT_RESISTANCE_PX).unwrap();

    // Act
    daemon
        .cross_edge(bounds, &[bounds], (-3, 50), (dx, 0), false)
        .await;

    // Assert
    let pending = daemon.wol.pending.as_ref().unwrap();
    assert_eq!(pending.peer, target);
    assert!(daemon.input.edge_no_target.is_none());
}

#[tokio::test]
async fn crossing_push_notch_enabled_only_accepts_top_push_inside_span() {
    // Arrange
    let (mut daemon, _dir, _) = placed_daemon(0);
    let bounds = (0, 0, 100, 100);
    let monitors = [bounds.into()];
    let (_, events) = tokio::sync::mpsc::channel(1);
    let (control, _) = tokio::sync::mpsc::channel(1);
    daemon.input.capture = Some(a7n_platform::input::Capture {
        bounds: bounds.into(),
        monitors: monitors.to_vec().into(),
        notch: Some((40, 0, 20, 5).into()),
        events,
        control,
    });
    daemon.config.input.notch_crossing = true;

    // Act
    let outside = daemon.crossing_push(bounds, &monitors, (20, 0), (0, -1), false);
    let inside = daemon.crossing_push(bounds, &monitors, (50, 0), (0, -1), false);

    // Assert
    assert!(outside.is_none());
    assert!(inside.is_some_and(|push| push.0 == Edge::Top));
}