use std::sync::Arc;
use std::time::Duration;
use xa11y::{Provider, ShellSurface, ShellSurfaceKind};
use super::types::{ComputerAct, ComputerScreenshot, ComputerSnapshot, SurfaceKind};
pub(crate) fn kind(kind: SurfaceKind) -> ShellSurfaceKind {
match kind {
SurfaceKind::MenuBar => ShellSurfaceKind::MenuBar,
SurfaceKind::StatusItems => ShellSurfaceKind::StatusItems,
SurfaceKind::Taskbar => ShellSurfaceKind::Taskbar,
SurfaceKind::Panel => ShellSurfaceKind::Panel,
SurfaceKind::Dock => ShellSurfaceKind::Dock,
SurfaceKind::Desktop => ShellSurfaceKind::Desktop,
SurfaceKind::Flyout => ShellSurfaceKind::Flyout,
SurfaceKind::Unknown => ShellSurfaceKind::Unknown,
}
}
fn validate_targeting(
tool: &str,
app_field: &str,
app: Option<&str>,
pid: Option<u32>,
surface: Option<SurfaceKind>,
allow_rootless: bool,
) -> Result<(), String> {
let app = app.map(str::trim).filter(|s| !s.is_empty());
let has_app = app.is_some();
let has_surface = surface.is_some();
let given = usize::from(has_app) + usize::from(pid.is_some()) + usize::from(has_surface);
if given == 0 {
return if allow_rootless {
Ok(())
} else {
Err(format!("{tool}: provide {app_field}, `pid` or `surface`"))
};
}
if given > 1 {
return Err(if has_app && pid.is_some() {
format!("{tool}: provide {app_field} or `pid`, not both")
} else {
format!(
"{tool}: target ONE of {app_field}/`pid`/`surface` — an app and a shell \
surface cannot share a call"
)
});
}
Ok(())
}
pub fn validate_snapshot(input: &ComputerSnapshot) -> Result<(), String> {
validate_targeting(
"computer_snapshot",
"`name`",
input.name.as_deref(),
input.pid,
input.surface,
false,
)
}
pub fn validate_act(step: &ComputerAct) -> Result<(), String> {
validate_targeting(
"computer_act",
"`name`",
step.name.as_deref(),
step.pid,
step.surface,
false,
)
}
pub fn validate_screenshot(input: &ComputerScreenshot) -> Result<(), String> {
validate_targeting(
"computer_screenshot",
"`app`",
input.app.as_deref(),
input.pid,
input.surface,
true,
)
}
pub fn resolve_with(
provider: Arc<dyn Provider>,
surface: SurfaceKind,
timeout: Duration,
) -> Result<ShellSurface, String> {
ShellSurface::by_kind_with(provider.clone(), kind(surface), timeout).map_err(|e| {
let census = ShellSurface::list_with(Arc::clone(&provider))
.map(|surfaces| {
if surfaces.is_empty() {
"census: 0 shell surfaces enumerated — this backend classifies \
AT-SPI Frames with `window-type: dock` only; WMs that draw \
their bar without one (i3bar, polybar, swaybar without a \
tray bridge) legitimately enumerate nothing"
.to_string()
} else {
format!(
"census: {} shell surface(s) present: {}",
surfaces.len(),
surfaces
.iter()
.map(|s| s.kind.to_snake_case())
.collect::<Vec<_>>()
.join(", ")
)
}
})
.unwrap_or_else(|e| format!("census unavailable: {e}"));
format!(
"computer surface `{}`: {e}; {census}",
surface_label(surface)
)
})
}
pub fn resolve(surface: SurfaceKind, timeout: Duration) -> Result<ShellSurface, String> {
let provider = xa11y::provider().map_err(|e| {
super::errors::render(
"computer",
&format!(
"initialize the accessibility provider (for surface `{}`)",
surface_label(surface)
),
&e,
)
})?;
resolve_with(provider, surface, timeout)
}
pub fn surface_label(surface: SurfaceKind) -> &'static str {
match surface {
SurfaceKind::MenuBar => "menu_bar",
SurfaceKind::StatusItems => "status_items",
SurfaceKind::Taskbar => "taskbar",
SurfaceKind::Panel => "panel",
SurfaceKind::Dock => "dock",
SurfaceKind::Desktop => "desktop",
SurfaceKind::Flyout => "flyout",
SurfaceKind::Unknown => "unknown",
}
}