aboutsummaryrefslogtreecommitdiffstats
path: root/crates/asist-tools/src/screen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/asist-tools/src/screen.rs')
-rw-r--r--crates/asist-tools/src/screen.rs88
1 files changed, 43 insertions, 45 deletions
diff --git a/crates/asist-tools/src/screen.rs b/crates/asist-tools/src/screen.rs
index dfed923..6fab402 100644
--- a/crates/asist-tools/src/screen.rs
+++ b/crates/asist-tools/src/screen.rs
@@ -1,21 +1,20 @@
-//! La pantalla como fuente de imágenes.
+//! The screen as an image source.
//!
-//! Se diferencia de la cámara en lo único que de verdad importa aquí: **una
-//! pantalla es texto**. Y con texto este modelo tiene un modo de fallo feo.
-//! Medido sobre una captura con tipografía de interfaz de 13 px, preguntando
-//! por datos concretos:
+//! It differs from the camera in the only thing that really matters here: **a
+//! screen is text**. And with text this model has an ugly failure mode.
+//! Measured on a capture with 13 px UI type, asking for specific details:
//!
-//! | Ancho | Tiempo | Aciertos | Qué pasa cuando falla |
-//! |------|--------|----------|-----------------------|
-//! | 640 | 2,4 s | 1 de 3 | se inventa el contenido |
-//! | 960 | 4,5 s | 2 de 3 | mezcla lo leído con lo supuesto |
-//! | 1280 | 7,6 s | 3 de 3 | — |
+//! | Width | Time | Hits | What happens when it fails |
+//! |-------|-------|--------|----------------------------|
+//! | 640 | 2.4 s | 1 of 3 | it makes the content up |
+//! | 960 | 4.5 s | 2 of 3 | it mixes what it read with what it assumed |
+//! | 1280 | 7.6 s | 3 of 3 | — |
//!
-//! A 640 px no dijo «no lo leo»: dijo que el error era «no se pudo abrir el
-//! archivo involution» y que la reunión era «a las 10:00». Ninguna de las dos
-//! cosas estaba en la imagen. De ahí que el valor por defecto sean 1280 px
-//! aunque cueste el triple que la cámara: para un asistente de voz, decir una
-//! hora equivocada con aplomo es peor que tardar cuatro segundos más.
+//! At 640 px it did not say «I cannot read it»: it said the error was «no se
+//! pudo abrir el archivo involution» and the meeting was «at 10:00». Neither
+//! was in the image. Hence the 1280 px default even though it costs three
+//! times the camera: for a voice assistant, confidently stating a wrong time
+//! is worse than taking four more seconds.
use std::path::PathBuf;
use std::time::Duration;
@@ -28,19 +27,18 @@ const TOOL: &str = "mirar_la_pantalla";
#[derive(Debug, Clone)]
pub struct ScreenConfig {
- /// Programa de captura y sus argumentos. `{ancho}` y `{salida}` se
- /// sustituyen antes de ejecutar. Tiene que escribir un JPEG por la salida
- /// estándar.
+ /// Capture program and its arguments. `{width}` and `{output}` are
+ /// substituted before running. It must write a JPEG to standard output.
pub command: Vec<String>,
- /// Ancho al que se reduce la captura antes de mandarla al modelo.
+ /// Width the capture is scaled down to before sending it to the model.
pub width: u32,
- /// Monitor concreto; vacío = todo lo que haya.
+ /// A specific monitor; empty = everything there is.
pub output: String,
pub timeout: Duration,
- /// Carpeta donde dejar las capturas. `None` = no se guarda ninguna.
+ /// Directory to leave captures in. `None` = none is saved.
///
- /// Aquí pesa más que en la cámara: en una captura de pantalla caben
- /// contraseñas, mensajes privados y correo abierto.
+ /// It weighs more here than for the camera: a screenshot can hold
+ /// passwords, private messages and open email.
pub save_dir: Option<PathBuf>,
}
@@ -48,9 +46,9 @@ impl Default for ScreenConfig {
fn default() -> Self {
Self {
command: vec![
- "scripts/capturar-pantalla.sh".into(),
- "{ancho}".into(),
- "{salida}".into(),
+ "scripts/capture-screen.sh".into(),
+ "{width}".into(),
+ "{output}".into(),
],
width: 1280,
output: String::new(),
@@ -72,7 +70,7 @@ impl Screen {
fn program(&self) -> Result<&String> {
self.config.command.first().ok_or_else(|| Error::Tool {
tool: TOOL.into(),
- message: "screen.command está vacío".into(),
+ message: "screen.command is empty".into(),
})
}
}
@@ -84,19 +82,19 @@ impl FrameSource for Screen {
fn available(&self) -> Result<()> {
let program = self.program()?;
- // Un nombre suelto se resuelve por el PATH; una ruta tiene que existir.
+ // A bare name is resolved through PATH; a path must exist.
if program.contains('/') && !std::path::Path::new(program).exists() {
return Err(Error::Tool {
tool: TOOL.into(),
message: format!("no existe {program}"),
});
}
- // Sin entorno gráfico no hay nada que capturar, y más vale decirlo al
- // arrancar que a mitad de una pregunta.
+ // Without a graphical environment there is nothing to capture, and
+ // better to say so at startup than in the middle of a question.
if std::env::var_os("WAYLAND_DISPLAY").is_none() && std::env::var_os("DISPLAY").is_none() {
return Err(Error::Tool {
tool: TOOL.into(),
- message: "no hay sesión gráfica (ni WAYLAND_DISPLAY ni DISPLAY)".into(),
+ message: "no graphical session (neither WAYLAND_DISPLAY nor DISPLAY)".into(),
});
}
Ok(())
@@ -108,8 +106,8 @@ impl FrameSource for Screen {
let args: Vec<String> = self.config.command[1..]
.iter()
.map(|arg| {
- arg.replace("{ancho}", &self.config.width.to_string())
- .replace("{salida}", &self.config.output)
+ arg.replace("{width}", &self.config.width.to_string())
+ .replace("{output}", &self.config.output)
})
.collect();
@@ -127,30 +125,30 @@ mod tests {
fn config() -> ScreenConfig {
ScreenConfig {
- command: vec!["/bin/echo".into(), "{ancho}".into(), "{salida}".into()],
+ command: vec!["/bin/echo".into(), "{width}".into(), "{output}".into()],
..Default::default()
}
}
#[test]
- fn el_ancho_por_defecto_es_el_minimo_medido_para_leer_texto() {
+ fn default_width_is_the_measured_minimum_to_read_text() {
assert_eq!(
ScreenConfig::default().width,
1280,
- "por debajo de 1280 el modelo se inventa lo que pone en pantalla"
+ "below 1280 the model makes up what the screen says"
);
}
#[test]
- fn por_defecto_no_se_guarda_ninguna_captura() {
+ fn no_capture_is_saved_by_default() {
assert!(
ScreenConfig::default().save_dir.is_none(),
- "en una captura de pantalla caben contraseñas y mensajes privados"
+ "a screenshot can hold passwords and private messages"
);
}
#[test]
- fn un_guion_inexistente_se_detecta_antes_de_registrar_la_herramienta() {
+ fn missing_script_is_detected_before_registering_the_tool() {
let screen = Screen::new(ScreenConfig {
command: vec!["/no/existe/captura.sh".into()],
..Default::default()
@@ -163,7 +161,7 @@ mod tests {
}
#[test]
- fn una_orden_vacia_se_rechaza() {
+ fn empty_command_is_rejected() {
let screen = Screen::new(ScreenConfig {
command: vec![],
..Default::default()
@@ -172,16 +170,16 @@ mod tests {
}
#[test]
- fn los_marcadores_se_sustituyen_antes_de_ejecutar() {
- // /bin/echo devuelve los argumentos, así que la captura falla por no
- // ser un JPEG; lo que se comprueba es que el mensaje trae el ancho ya
- // sustituido, no el marcador.
+ fn placeholders_are_substituted_before_running() {
+ // /bin/echo returns the arguments, so the capture fails for not being
+ // a JPEG; what is checked is that the message carries the width already
+ // substituted, not the placeholder.
let mut config = config();
config.width = 1280;
config.output = "eDP-1".into();
let screen = Screen::new(config);
if std::env::var_os("WAYLAND_DISPLAY").is_none() && std::env::var_os("DISPLAY").is_none() {
- return; // sin sesión gráfica no hay nada que probar aquí
+ return; // without a graphical session there is nothing to test here
}
let err = screen.capture().unwrap_err().to_string();
assert!(err.contains("no devolvió un JPEG"), "{err}");