diff --git a/package.json b/package.json index d0d326b..3c7f850 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.5", + "version": "0.1.6", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 72241e2..836437a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.5" +version = "0.1.6" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/src/printers.rs b/src-tauri/src/printers.rs index e6dee89..636ee3c 100644 --- a/src-tauri/src/printers.rs +++ b/src-tauri/src/printers.rs @@ -52,6 +52,11 @@ pub fn list_system_printers() -> Result> { } pub fn virtual_printer_status() -> Result { + #[cfg(target_os = "linux")] + { + return linux_virtual_printer_status(); + } + let installed = list_system_printers()? .iter() .any(|printer| is_virtual_printer_name(&printer.name)); @@ -270,6 +275,13 @@ fn list_linux_printers() -> Result> { let Some((name, uri)) = rest.split_once(": ") else { continue; }; + // Ignore placeholder/dummy queues. They can expose a PPD but are not a + // usable physical destination and can make Chromium reject the cloned + // FieldLens queue as "not installed correctly". + if uri == "///dev/null" || uri == "file:///dev/null" { + continue; + } + let address = socket_uri_to_address(uri); printers.push(SystemPrinter { name: name.to_string(), @@ -296,6 +308,67 @@ fn socket_uri_to_address(uri: &str) -> Option { } } +#[cfg(target_os = "linux")] +fn linux_virtual_printer_status() -> Result { + let printers = list_linux_printers()?; + let installed = printers + .iter() + .any(|printer| is_virtual_printer_name(&printer.name)); + + if !installed { + return Ok(VirtualPrinterStatus { + installed: false, + name: VIRTUAL_PRINTER_NAME.into(), + detail: "Not installed yet. Choose the physical destination printer, then create it.".into(), + }); + } + + let state = Command::new("lpstat") + .args(["-p", "FieldLens-Print"]) + .output() + .context("query FieldLens Print CUPS state")?; + let accepting = Command::new("lpstat") + .args(["-a", "FieldLens-Print"]) + .output() + .context("query FieldLens Print accepting state")?; + + let state_text = String::from_utf8_lossy(&state.stdout); + let accepting_text = String::from_utf8_lossy(&accepting.stdout); + let enabled = state.status.success() + && !state_text.to_ascii_lowercase().contains("disabled"); + let accepts_jobs = accepting.status.success() + && accepting_text.to_ascii_lowercase().contains("accepting requests"); + let ready = enabled && accepts_jobs; + + Ok(VirtualPrinterStatus { + installed: ready, + name: VIRTUAL_PRINTER_NAME.into(), + detail: if ready { + "Ready and accepting jobs in the system print dialog.".into() + } else { + "FieldLens Print exists but CUPS has it disabled or not accepting jobs. Click Create / repair FieldLens Print.".into() + }, + }) +} + +#[cfg(target_os = "linux")] +fn run_cups_repair_command(program: &str, args: &[&str]) -> Result<()> { + let direct = Command::new(program).args(args).status(); + if matches!(direct, Ok(status) if status.success()) { + return Ok(()); + } + + let status = Command::new("pkexec") + .arg(program) + .args(args) + .status() + .with_context(|| format!("run elevated {program}"))?; + if !status.success() { + return Err(anyhow!("{program} failed; administrator approval may be required")); + } + Ok(()) +} + #[cfg(target_os = "linux")] fn install_linux_virtual_printer(target_printer: &str) -> Result<()> { let printers = list_linux_printers()?; @@ -326,21 +399,24 @@ fn install_linux_virtual_printer(target_printer: &str) -> Result<()> { args.push(VIRTUAL_PRINTER_NAME.to_string()); let direct = Command::new("lpadmin").args(&args).status(); - if matches!(direct, Ok(status) if status.success()) { - return Ok(()); + if !matches!(direct, Ok(status) if status.success()) { + let status = Command::new("pkexec") + .arg("lpadmin") + .args(&args) + .status() + .context("run elevated CUPS printer setup")?; + + if !status.success() { + return Err(anyhow!( + "CUPS printer setup failed; administrator approval may be required" + )); + } } - let status = Command::new("pkexec") - .arg("lpadmin") - .args(&args) - .status() - .context("run elevated CUPS printer setup")?; - - if !status.success() { - return Err(anyhow!( - "CUPS printer setup failed; administrator approval may be required" - )); - } + // lpadmin -E normally enables the queue, but explicitly repair both CUPS + // state flags because Chromium disables Print when either is stale. + run_cups_repair_command("cupsenable", &["FieldLens-Print"])?; + run_cups_repair_command("cupsaccept", &["FieldLens-Print"])?; Ok(()) } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index a589ab9..2b83301 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.5", + "version": "0.1.6", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index 80a0311..92d1616 100644 --- a/src/main.ts +++ b/src/main.ts @@ -85,7 +85,7 @@ let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.5' +let currentVersion = '0.1.6' let bundleType: BundleType | null = null let availableUpdate: Update | null = null let availableManualUpdate: ManualUpdate | null = null