use a7n_ipc::config::{Config, Modifier};
use a7n_proto::v1::{ScrollEvent, ScrollUnit};
use super::*;
use crate::command::Command;
fn modifier(down: bool, modifier: Modifier) -> InputEvent {
InputEvent {
event: Some(input_event::Event::Key(KeyEvent {
usage: modifier.usage(),
down,
character: String::new(),
})),
}
}
fn save(peer: &Peer, text: &str) {
Config::parse(text)
.unwrap()
.save(&peer.dir.path().join("config.toml"))
.unwrap();
}
#[tokio::test]
async fn reload_config_denied_owner_releases_keys_and_returns_owner_home() {
let (a, b, mut a_input, mut b_input) = pair().await;
drive(&mut a_input, &mut b_input).await;
a_input
.capture
.send(Captured::Input(key(true)))
.await
.unwrap();
assert!(matches!(b_input.injected().await, Inject::Event(event) if event == key(true)));
save(&b, &format!("[peers.{}]\nallow_drive = false\n", a.machine));
b.command(Command::ReloadConfig).await.unwrap();
assert!(matches!(b_input.injected().await, Inject::Event(event) if event == key(false)));
b_input.grab(false).await;
a_input.grab(false).await;
a.stop().await;
b.stop().await;
}
#[tokio::test]
async fn reload_config_invalid_file_keeps_previous_input_settings() {
let (a, b, mut a_input, mut b_input) = pair().await;
drive(&mut a_input, &mut b_input).await;
std::fs::write(b.dir.path().join("config.toml"), "[input]\nspeed = 0\n").unwrap();
let result = b.command(Command::ReloadConfig).await;
assert!(matches!(result, Err(DaemonError::Config(_))));
a_input
.capture
.send(Captured::Pointer {
x: 50,
y: 50,
dx: 4,
dy: -6,
})
.await
.unwrap();
assert!(matches!(
b_input.injected().await,
Inject::Motion { dx: 4, dy: -6 }
));
a.stop().await;
b.stop().await;
}
#[tokio::test]
async fn forward_capture_reload_mid_press_preserves_remap_until_next_take() {
let (a, b, mut a_input, mut b_input) = configured_pair(|a, b| {
a.config
.set(
&format!("peers.{}.modifier_mode", b.identity.machine_id),
"custom",
)
.unwrap();
a.config
.set(
&format!("peers.{}.modifier_remap.left_meta", b.identity.machine_id),
"left_ctrl",
)
.unwrap();
})
.await;
drive(&mut a_input, &mut b_input).await;
a_input
.capture
.send(Captured::Input(modifier(true, Modifier::LeftMeta)))
.await
.unwrap();
let pressed = b_input.injected().await;
save(
&a,
&format!(
"[peers.{}]\nmodifier_mode = \"custom\"\nmodifier_remap = {{ left_meta = \"left_alt\" }}\n",
b.machine
),
);
a.command(Command::ReloadConfig).await.unwrap();
a_input
.capture
.send(Captured::Input(modifier(false, Modifier::LeftMeta)))
.await
.unwrap();
let released = b_input.injected().await;
assert!(matches!(pressed, Inject::Event(event) if event == modifier(true, Modifier::LeftCtrl)));
assert!(
matches!(released, Inject::Event(event) if event == modifier(false, Modifier::LeftCtrl))
);
b_input.feedback.send(pointer(false)).await.unwrap();
a_input.grab(false).await;
b_input.grab(false).await;
a_input.cross(true).await;
a_input.grab(true).await;
assert!(matches!(b_input.injected().await, Inject::Warp { .. }));
a_input
.capture
.send(Captured::Input(modifier(true, Modifier::LeftMeta)))
.await
.unwrap();
assert!(
matches!(b_input.injected().await, Inject::Event(event) if event == modifier(true, Modifier::LeftAlt))
);
a.stop().await;
b.stop().await;
}
#[tokio::test]
async fn reload_config_input_settings_apply_scrolling_and_receiver_speed() {
let (a, b, mut a_input, mut b_input) = pair().await;
drive(&mut a_input, &mut b_input).await;
save(
&a,
"[sync]\nsettings = false\n[input]\nnatural_scrolling = true\n",
);
save(&b, "[sync]\nsettings = false\n[input]\nspeed = 0.5\n");
let scroll = InputEvent {
event: Some(input_event::Event::Scroll(ScrollEvent {
dx: 2.0,
dy: -3.0,
unit: ScrollUnit::Line.into(),
})),
};
a.command(Command::ReloadConfig).await.unwrap();
b.command(Command::ReloadConfig).await.unwrap();
a_input
.capture
.send(Captured::Pointer {
x: 50,
y: 50,
dx: 8,
dy: -6,
})
.await
.unwrap();
a_input.capture.send(Captured::Input(scroll)).await.unwrap();
assert!(matches!(
b_input.injected().await,
Inject::Motion { dx: 4, dy: -3 }
));
loop {
if let Inject::Event(event) = b_input.injected().await {
assert!(matches!(
event.event,
Some(input_event::Event::Scroll(ScrollEvent {
dx: -2.0,
dy: 3.0,
..
}))
));
break;
}
}
a.stop().await;
b.stop().await;
}
#[tokio::test]
async fn reload_config_synced_speed_changes_peer_file_and_live_motion() {
let (a, b, mut a_input, mut b_input) = pair().await;
timeout(TEST_TIMEOUT, async {
loop {
let a_state =
crate::settings_store::SettingsStore::load(&a.dir.path().join("settings.pb"));
let b_state =
crate::settings_store::SettingsStore::load(&b.dir.path().join("settings.pb"));
if a_state.state.is_some() && a_state.state == b_state.state {
break;
}
tokio::task::yield_now().await;
}
})
.await
.unwrap();
save(&a, "[input]\nspeed = 2.0\n");
a.command(Command::ReloadConfig).await.unwrap();
timeout(TEST_TIMEOUT, async {
loop {
if Config::load(&b.dir.path().join("config.toml"))
.unwrap()
.input
.speed
.to_bits()
== 2.0_f64.to_bits()
{
break;
}
tokio::task::yield_now().await;
}
})
.await
.unwrap();
drive(&mut a_input, &mut b_input).await;
a_input
.capture
.send(Captured::Pointer {
x: 50,
y: 50,
dx: 4,
dy: -6,
})
.await
.unwrap();
assert!(matches!(
b_input.injected().await,
Inject::Motion { dx: 8, dy: -12 }
));
a.stop().await;
b.stop().await;
}
#[tokio::test]
async fn forward_capture_modifier_modes_apply_sender_policy() {
for (mode, expected) in [
("auto", Modifier::LeftCtrl),
("off", Modifier::LeftMeta),
("custom", Modifier::LeftAlt),
] {
let (a, b, mut a_input, mut b_input) = configured_pair(|a, b| {
a.info.platform = a7n_proto::v1::Platform::Macos.into();
b.info.platform = a7n_proto::v1::Platform::Windows.into();
let prefix = format!("peers.{}", b.identity.machine_id);
a.config
.set_many(
[
(format!("{prefix}.modifier_mode"), mode),
(format!("{prefix}.modifier_remap.left_meta"), "left_alt"),
]
.iter()
.map(|(key, value)| (key.as_str(), *value)),
)
.unwrap();
})
.await;
drive(&mut a_input, &mut b_input).await;
for down in [true, false] {
a_input
.capture
.send(Captured::Input(modifier(down, Modifier::LeftMeta)))
.await
.unwrap();
assert!(
matches!(b_input.injected().await, Inject::Event(event) if event == modifier(down, expected)),
"{mode}"
);
}
a.stop().await;
b.stop().await;
}
}