feat(linux): add no-paper capture pipeline self-test
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled

This commit is contained in:
Admin 2026-09-24 05:20:35 +00:00
parent a65b79e886
commit 292a97232b
3 changed files with 99 additions and 0 deletions

View File

@ -96,6 +96,82 @@ fn poll_login(state: State<'_, AppState>) -> Result<LoginPoll, String> {
fieldlens::poll_login(&state.shared).map_err(|error| error.to_string())
}
#[cfg(target_os = "linux")]
fn build_self_test_pdf() -> Vec<u8> {
let objects = [
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n".to_string(),
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n".to_string(),
"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>\nendobj\n".to_string(),
{
let stream = "BT /F1 18 Tf 72 720 Td (FieldLens capture self-test) Tj ET\n";
format!("4 0 obj\n<< /Length {} >>\nstream\n{}endstream\nendobj\n", stream.len(), stream)
},
"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n".to_string(),
];
let mut pdf = b"%PDF-1.4\n".to_vec();
let mut offsets = Vec::new();
for object in &objects {
offsets.push(pdf.len());
pdf.extend_from_slice(object.as_bytes());
}
let xref = pdf.len();
pdf.extend_from_slice(format!("xref\n0 {}\n", objects.len() + 1).as_bytes());
pdf.extend_from_slice(b"0000000000 65535 f \n");
for offset in offsets {
pdf.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
}
pdf.extend_from_slice(
format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n",
objects.len() + 1,
xref
)
.as_bytes(),
);
pdf
}
#[tauri::command]
fn run_capture_self_test() -> Result<String, String> {
#[cfg(target_os = "linux")]
{
let path = std::env::temp_dir().join(format!(
"fieldlens-capture-self-test-{}.pdf",
uuid::Uuid::new_v4()
));
std::fs::write(&path, build_self_test_pdf()).map_err(|error| error.to_string())?;
let output = std::process::Command::new("lp")
.args(["-d", "FieldLens-Test-Capture"])
.arg(&path)
.output()
.map_err(|error| error.to_string());
let _ = std::fs::remove_file(&path);
let output = output?;
if !output.status.success() {
return Err(format!(
"CUPS self-test submission failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
let detail = String::from_utf8_lossy(&output.stdout).trim().to_string();
return Ok(if detail.is_empty() {
"Self-test submitted to FieldLens Test Capture.".into()
} else {
detail
});
}
#[cfg(not(target_os = "linux"))]
{
Err("Capture self-test is currently available on Linux only.".into())
}
}
#[tauri::command]
fn sync_pending(state: State<'_, AppState>) -> Result<SyncResult, String> {
fieldlens::sync_pending(&state.shared).map_err(|error| error.to_string())
@ -127,6 +203,7 @@ pub fn run() {
get_virtual_printer_status,
install_virtual_printer,
remove_virtual_printer,
run_capture_self_test,
begin_login,
poll_login,
sync_pending

View File

@ -152,6 +152,7 @@ app.innerHTML = `
<div class="actions printer-actions">
<button class="btn primary" id="install-printer">Create / repair printers</button>
<button class="btn ghost" id="self-test">Run capture self-test</button>
<button class="btn ghost danger" id="remove-printer">Remove</button>
</div>
@ -471,6 +472,22 @@ el('install-printer').addEventListener('click', async () => {
}
})
el('self-test').addEventListener('click', async () => {
const button = el<HTMLButtonElement>('self-test')
const state = el('virtual-printer-state')
button.disabled = true
state.textContent = 'Submitting a no-paper capture self-test…'
try {
const result = await invoke<string>('run_capture_self_test')
state.textContent = result
window.setTimeout(() => { void refreshStatus() }, 1200)
} catch (error) {
state.textContent = `Self-test failed: ${String(error)}`
} finally {
button.disabled = false
}
})
el('remove-printer').addEventListener('click', async () => {
const button = el<HTMLButtonElement>('remove-printer')
const state = el('virtual-printer-state')

View File

@ -532,3 +532,8 @@ select:focus {
.job { grid-template-columns: 1fr; }
.job-meta { justify-content: flex-start; text-align: left; }
}
#self-test {
color: #9fcef5;
border-color: #294865;
}