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
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:
parent
a65b79e886
commit
292a97232b
@ -96,6 +96,82 @@ fn poll_login(state: State<'_, AppState>) -> Result<LoginPoll, String> {
|
|||||||
fieldlens::poll_login(&state.shared).map_err(|error| error.to_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]
|
#[tauri::command]
|
||||||
fn sync_pending(state: State<'_, AppState>) -> Result<SyncResult, String> {
|
fn sync_pending(state: State<'_, AppState>) -> Result<SyncResult, String> {
|
||||||
fieldlens::sync_pending(&state.shared).map_err(|error| error.to_string())
|
fieldlens::sync_pending(&state.shared).map_err(|error| error.to_string())
|
||||||
@ -127,6 +203,7 @@ pub fn run() {
|
|||||||
get_virtual_printer_status,
|
get_virtual_printer_status,
|
||||||
install_virtual_printer,
|
install_virtual_printer,
|
||||||
remove_virtual_printer,
|
remove_virtual_printer,
|
||||||
|
run_capture_self_test,
|
||||||
begin_login,
|
begin_login,
|
||||||
poll_login,
|
poll_login,
|
||||||
sync_pending
|
sync_pending
|
||||||
|
|||||||
17
src/main.ts
17
src/main.ts
@ -152,6 +152,7 @@ app.innerHTML = `
|
|||||||
|
|
||||||
<div class="actions printer-actions">
|
<div class="actions printer-actions">
|
||||||
<button class="btn primary" id="install-printer">Create / repair printers</button>
|
<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>
|
<button class="btn ghost danger" id="remove-printer">Remove</button>
|
||||||
</div>
|
</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 () => {
|
el('remove-printer').addEventListener('click', async () => {
|
||||||
const button = el<HTMLButtonElement>('remove-printer')
|
const button = el<HTMLButtonElement>('remove-printer')
|
||||||
const state = el('virtual-printer-state')
|
const state = el('virtual-printer-state')
|
||||||
|
|||||||
@ -532,3 +532,8 @@ select:focus {
|
|||||||
.job { grid-template-columns: 1fr; }
|
.job { grid-template-columns: 1fr; }
|
||||||
.job-meta { justify-content: flex-start; text-align: left; }
|
.job-meta { justify-content: flex-start; text-align: left; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#self-test {
|
||||||
|
color: #9fcef5;
|
||||||
|
border-color: #294865;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user