1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use clap::Parser;
use wdotool::{cli::Command, diag, Cli, DispatchCtx, ExitCode};
use wdotool_core::detector::{self, BackendKind, Environment};
use wdotool_core::WdoError;
#[cfg(feature = "recorder")]
use wdotool::record;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
wdotool::init_tracing(cli.verbose);
// Diag has to short-circuit before detector::build so the probes
// never touch the portal session (which would pop a consent dialog
// for libei users, exactly the surprise diag is meant to remove).
if let Command::Diag { json, copy } = cli.command {
let format = if json {
diag::DiagFormat::Json
} else {
diag::DiagFormat::Markdown
};
return diag::run(format, copy);
}
// Record short-circuits before detector::build for the same
// reason Diag does: the recorder owns its own portal session
// (libei in receiver mode) and doesn't need a sender Backend
// to bootstrap.
#[cfg(feature = "recorder")]
if let Command::Record {
output,
max_duration,
backend,
} = cli.command
{
return record::run(output, max_duration, backend).await;
}
let env = Environment::detect();
// Prime short-circuits the dispatch loop so it can hold the
// wlr-protocols backend alive in the foreground until a signal
// arrives. We always pick wlr-protocols regardless of cli.backend
// because that's the only backend whose devices live across calls.
if let Command::Prime = cli.command {
let backend = detector::build(&env, Some(BackendKind::WlrProtocols)).await?;
// Stdout is the readiness signal for any test harness (or
// human) waiting on us. Flush so the `ready` line lands
// before this process blocks on the signal handler.
println!("ready");
std::io::Write::flush(&mut std::io::stdout()).ok();
// Block until Ctrl-C or SIGTERM. Both end the wait the same
// way; the backend (and its virtual devices) gets dropped
// when this function returns, which cleanly releases the
// devices in the compositor.
tokio::signal::ctrl_c().await?;
drop(backend);
return Ok(());
}
let forced = match cli.backend.as_deref() {
Some(s) => Some(BackendKind::parse(s).ok_or_else(|| {
WdoError::InvalidArg(format!(
"unknown backend '{s}' (expected libei, wlr-protocols, kde, gnome, uinput)"
))
})?),
None => None,
};
let backend = detector::build(&env, forced).await?;
let exit = {
let mut stdout = std::io::stdout().lock();
let mut stderr = std::io::stderr().lock();
let mut ctx = DispatchCtx {
backend: &*backend,
env: &env,
stdout: &mut stdout,
stderr: &mut stderr,
};
wdotool::dispatch(&mut ctx, cli.command).await?
};
if exit != ExitCode::SUCCESS {
std::process::exit(exit.0);
}
Ok(())
}